力扣 93 - 复原 IP 地址【回溯算法】
@TOC
一、题目分析
题目描述
有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。
例如:"0.1.2.201" 和 "192.168.1.1" 是 有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是 无效 IP 地址。给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你 不能 重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。
示例
输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
输入:s = "0000" 输出:["0.0.0.0"]
输入:s = "101023" 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
思路分析
首先根据题目本身可以看到这是一道涉及字符串分割的问题,是属于回溯算法的一种,不太了解此算法的小伙伴可以先去了解一下,回溯算法它是 DFS(深度优先搜索)的一种,与 DFS 不同的是,它不会一直向下遍历,会回溯寻找其他方案。就如此题一样,至于如何分割,我==画了一张图==,大家一看便知
怎么样,是不是很清晰,对此题有了一些基本的了解,接下去继续分割就形成了一个树形结构,这是数据结构中的一种,不懂的同学可以去了解一下。举个例子,对于第一个 IP 串,已经截取了 2,接下去它可以再截取 5,或者是 55,但是 552 就不行了,因为其超出了 255 的范围界限,然后用回溯去一步步地判断每一个字段是否合法即可,且听我慢慢道来:mag_right:
二、代码的细究与详解
回溯三部曲
①递归函数的返回值以及参数
首先我们要考虑的就是这个递归函数的返回值以及参数,对于返回值,一般都是 void,有些特殊情况写成 int 或其他类型,第一个参数是传入的待处理字符串 s,而 startindex 是每次对于字符串要从哪里开始分割的起始位置,因为每次回溯都需要从不同的位置开始,最后的 pointnum 指的是 IP 地址所需要的逗点“.”数量。题目中有讲述到 IP 地址是由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0)组成的,因此最多就只能有三个逗点,这也是我们要进行的第二步,判断其递归函数的递归出口
②递归出口
对于此递归出口,就是我如上所说的逗点到达 3 的的判断,而且在内部,还需要判断你传进来的从 startindex 起始位置到 i 终止位置的字段串是否合法,因为不仅是要判断整个 IP 是否合法,而且对于每一个字段的判断也是很重要的,这样才能筛选出最正确又合法的 IP 地址,怎么样,准备开始==头脑风暴==:ocean:吧,好戏才刚刚开始,如果判断其是合法的字段,则将其放入 result 这个结果集中,它的定义是这样的
这个的话大家其实看给出主接口函数集合,看它的返回值是什么,那一定要在私有变量定义区 private 中定义一个与其返回值相同的容器来存储和接收合法的 IP
③单层搜索的逻辑
对于此单层搜索逻辑的判断,我们可以看到循环都是从 startindx 开始的,直到字符串的末尾,去逐一判断,那判断 IP 地址是否合法这个功能我们是不是在整道题中都需要用到呢?答案是的,因此我们要将其封装成一个函数,需要的时候将其调用即可,接下来 insert()就是插入函数在其后插入逗点,前一个参数是位置迭代器,begin()是起始迭代器,+ i + 1 就到了当前字段的末尾,后一个参数便是所要插入的内容“.”,因为插入了一个逗点,所以 pointnum 就要++,以便可以传入 backtracking 函数进行下一次层的递归,因为我们上面讲到了 pointnum==3,所以这个 pointnum 是一直在递增的。
对于 startindex 这个位置==为什么传入 i + 2==我解释一下,首先因为是回溯,你要遍历到下一个树枝,而下一个树枝也就是下一个字段,它的起始位置和上一个字段是不一样的,所以要 i + 1,而为什么要 i + 1 再+ 1 呢,是因为你插入了一个逗点了,这个逗点也是需要占一个位置的,所以要从 i + 2 个位置开始遍历
下面的函数是判断 IP 是否合法的
可以知道,判断一个 IP 是否合法,返回值类型只有 true 和 false,因此要设置为 bool 类型,对于内容,肯定是要考虑到尽可能多的情况。
这里的第一种情况就是 start > end,也就是你传进来的字符截取得越界了,也就是不合法;
第二种便是要考虑到开头为数字'0'的情况,这个题目本身已经告诉我们了,而且还要考虑的一种情况就是字段串单独为 0 的这一种,这个题目也给出事例了,如果忘了可以拉上去看一下,因为==0.0.0.0==这种 IP 也是合法的,这是一种特殊地址,一般在网络编程的时候会使用。所以回归题目,这种情况就是 start == end 的情况,所以当 start != end 时,这个字段串就不止一个数字,一定是两个及两个以上的,例如 01,03,005 之类的,这些就可以判断出其不合法
对于第三种也及时其他情况,我们就需要去循环遍历判断了,从传入的 start 到 end,如果碰见了 str[i] < '0' || str[i] > '9'的这种情况,那这个数串一定是不合法的,可以判断其为非法数字
最后一种,我们还需要考虑到也就是题目给出的 IP 不可越界的情况,这就需要我们去思考了,因为每次你继续分割的 IP 要么是分割一个、两个最多也就是三个,不会出现四个的可能,这就对应了我们的数字中的==个位十位和百位==,这一点及其重要,大家要理解这个,因为三个位上的数字相差的 10 倍的,因为我们要乘上 10 去依次判断,因为每次回溯之后你进入下一分段的判断这个位数肯定上一次的十倍,上一次分割了一位,那这次就分割两位。但后面为什么要用 str[i] - '0'呢,这其实就是一个基本操作,你想想,因为我们拿到的是一个 string 字符串呀,怎么能对字符串进行一个数字运算呢,所以将其转化数字才对嘛,最后转化后的数字如果> 255 则也可以认定为是不合法的,返回 false
最后当所有不合法的情况都判断完,那剩下的就是合法,返回 true 即可,有一点要注意的是以后大家在写判断函数的时候不要在内部判断如果合法就返回 true,因为这样的话其他的就判断不了了,一定要在判断完所有最坏情况下才能安心返回 true.讲到这里你有没有觉得自己又头脑风暴了呢,如果累了就休息一下,再讲整体的逻辑理一遍,对于初学回溯的人来说这个题还是很难理解的(高手请无视:dog:)。
OK,说完了回溯三部曲后,你是不是对这道题的整体逻辑和框架结构有所了解了呢,如果还没有很清楚的话,我这里将上面的图又继续地画了一遍,大家可以对照着讲解再看一看
最后就是这个主函数的调用
解释一下这个 s.size() < 4 || s.size() > 12 是什么意思,这步的话其实就是一个==剪枝操作==,回溯的话如果你学地再好一些应该要学会灵活地剪枝,也就是我上图画的不合法的分段,试想如果将这些不合法的分段在递归函数调用之前就减去,那是不是又可以减少这运行的时间了呢,这其实就是一个对代码优化的过程,到后面慢慢地也会习惯。
好,我们回归正题,这里给出两种极端的 IP 地址,仔细想想应该也可以考虑到,有一种其实题目已经给出了,数一下两种情况各自所用的位数(不包含逗点)就很清楚了,第一种是 4,第二种是 12,那合法的 IP 是不是肯定在这两种情况之间呐,就像 begin 和 end 一样,如果是超过它们的两端,那就不对了,就直接 return result 结果即可
三、相似题目
这道提的话也是比较经典的回溯分割串问题,可以去练练手:snowman:131.分割回文串
四、总结与提炼
说完了这道力扣上编号为 93 的题目,你对回溯算法有没有一些初步或者加深的理解了呢,但这么一道题目是完全不够的,回溯算法有五大类问题,分别是组合、割串、子集、排列和棋盘问题,有兴趣想要深入了解的可以再去做做对应 LeetCode 上的题目,后续会继续更新关于回溯类的题目,敬请期待。最后,感谢您对本文的阅读,如有问题请于评论区或私信指出,谢谢:cherry_blossom:
版权声明: 本文为 InfoQ 作者【Fire_Shield】的原创文章。
原文链接:【http://xie.infoq.cn/article/0d08b892888944921b11c0ec4】。文章转载请联系作者。
评论