LeetCode : 93. Restore IP Addresses
这又是一道看上去就是“我一定能做出来,但是不是有更好的方法呢”的问题。
我的第一反应是概率论里往样本中插隔板的模型,只要判断每个隔板间是不是0-255就行了,在11个空中间插3个点,是不是用循环更好些?
class Solution { public: vector<string> restoreIpAddresses(string s) { vector<string> result; result.clear(); int counter=0; int len = s.length(); if(len>12||len<4) return result; for(int i=1;i<=3;i++){ for(int j=1;j<=3;j++){ for(int k=1;k<=3;k++){ if(i+j+k>=len) continue; string p1 = s.substr(0,i); if(p1.length()>1&&p1[0]=='0') continue; int v1 = atoi(p1.c_str()); if(v1>255) continue; string p2 = s.substr(i,j); if(p2.length()>1&&p2[0]=='0') continue; int v2 = atoi(p2.c_str()); if(v2>255) continue; string p3 = s.substr(i+j,k); if(p3.length()>1&&p3[0]=='0') continue; int v3 = atoi(p3.c_str()); if(v3>255) continue; string p4 = s.substr(i+j+k,len-i-j-k); if(p4.length()>1&&p4[0]=='0') continue; int v4 = atoi(p4.c_str()); if(v4>255) continue; string temp = p1+"."+p2+"."+p3+"."+p4; result.push_back(temp); } } } return result; } };
我的代码如上,里面还需要考虑一些首位是0的情况,所以长了一些。但继续发散考虑一下,如果题目更加宽泛,我们需要的不是四段,而是几十段怎么办,或者干脆段数就是个变量呢?
显然如果这种情况发生,循环是解决不了问题的,那就只能用递归了。所以这道题目还可以用深度优先算法DFS来解决,注意处理返回条件和回溯条件就好了。
我这里直接放了一段别人分享的代码:
class Solution { public: static const int numSeg = 4; void dfs(int start, int seg, string& s, string path, vector<string>& result) { if(seg == numSeg && start == s.size()) { path.erase(path.end()-1); result.push_back(path); return; } if(s.size() - start < (numSeg - seg)) return; if(s.size() - start > 3 * (numSeg - seg)) return; int num = 0; string ip; for(int i = start; i < start+3; ++i) { num = num*10 + (s[i]-'0'); if(num < 256) { ip += s[i]; dfs(i+1, seg+1, s, path+ip+'.', result); } if(ip[0] == '0') break; } } vector<string> restoreIpAddresses(string s) { vector<string> result; string path; if(s.size() < numSeg || s.size() > 3 * numSeg) return result; dfs(0, 0, s, path, result); return result; } };
这里决定复习一下常见的算法,毕竟长时间不用,很多记忆都模糊了:
回溯法
回溯算法实际上是一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
回溯法基本思想
在包含问题的所有解的解空间树中,按照深度优先搜索的策略,从根结点出发深度探索解空间树。当探索到某一结点时,要先判断该结点是否包含问题的解,如果包含,就从该结点出发继续探索下去,如果该结点不包含问题的解,则逐层向其祖先结点回溯。若用回溯法求问题的所有解时,要回溯到根,且根结点的所有可行的子树都要已被搜索遍才结束。而若使用回溯法求任一个解时,只要搜索到问题的一个解就可以结束。
用回溯法解题的一般步骤:
-针对所给问题,确定问题的解空间
-确定结点的扩展搜索规则
-以深度优先方式搜索解空间,并在搜索过程中用剪枝函数避免无效搜索
分支限界法
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解。
分支搜索算法
所谓“分支”就是采用广度优先的策略,依次搜索E-结点的所有分支,也就是所有相邻结点,抛弃不满足约束条件的结点,其余结点加入活结点表。然后从表中选择一个结点作为下一个E-结点,继续搜索。选择下一个E-结点的方式不同,则会有几种不同的分支搜索方式:
-FIFO搜索
-LIFO搜索
-优先队列式搜索
分支限界法的一般过程
由于求解目标不同,导致分支限界法与回溯法在解空间树T上的搜索方式也不相同。回溯法以深度优先的方式搜索解空间树T,而分支限界法则以广度优先或以最小耗费优先的方式搜索。分支限界法的搜索策略是:在扩展结点处,先生成其所有的儿子结点(分支),然后再从当前的活结点表中选择下一个扩展对点。为了有效地选择下一扩展结点,以加速搜索的进程,在每一活结点处,计算一个函数值(限界),并根据这些已计算出的函数值,从当前活结点表中选择一个最有利的结点作为扩展结点,使搜索朝着解空间树上有最优解的分支推进,以便尽快地找出一个最优解。
分支限界法常以广度优先或以最小耗费(最大效益)优先的方式搜索问题的解空间树。问题的解空间树是表示问题解空间的一棵有序树,常见的有子集树和排列树。 在搜索问题的解空间树时,分支限界法与回溯法对当前扩展结点所使用的扩展方式不同。在分支限界法中,每一个活结点只有一次机会成为扩展结点。活结点一旦成 为扩展结点,就一次性产生其所有儿子结点。在这些儿子结点中,那些导致不可行解或导致非最优解的儿子结点被舍弃,其余儿子结点被子加入活结点表中。此后, 从活结点表中取下一结点成为当前扩展结点,并重复上述结点扩展过程。这个过程一直持续到找到所求的解或活结点表为空时为止。
回溯法和分支限界法的一些区别
-方法对解空间树的搜索方式
-存储结点的常用数据结构
-结点存储特性常用应用
-回溯法深度优先搜索堆栈活结点的所有可行子结点被遍历后才被从栈中弹出找出满足约束条件的所有解
-分支限界法广度优先或最小消耗优先搜索队列、优先队列每个结点只有一次成为活结点的机会找出满足约束条件的一个解或特定意义下的最优解