LeetCode93.复原IP地址

368 阅读1分钟

「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

题目

有效 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 中的任何数字。你可以按 任何 顺序返回答案。

 

示例 1:

  • 输入:s = "25525511135"
  • 输出:["255.255.11.135","255.255.111.35"]

示例 2:

  • 输入:s = "0000"
  • 输出:["0.0.0.0"]

示例 3:

  • 输入:s = "1111"
  • 输出:["1.1.1.1"]

示例 4:

  • 输入:s = "010010"
  • 输出:["0.10.0.10","0.100.1.0"]

示例 5:

  • 输入:s = "101023"
  • 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]

提示:

  • 0 <= s.length <= 20
  • s 仅由数字组成

解题思路

以 "25525511135" 为例,第一步时我们有几种选择?

  • 选 "2" 作为第一个片段
  • 选 "25" 作为第一个片段
  • 选 "255" 作为第一个片段 能切三种不同的长度,切第二个片段时,又面临三种选择。 这会向下分支形成一棵树,我们用 DFS 去遍历所有选择,必要时提前回溯。 因为某一步的选择可能是错的,得不到正确的结果,不要往下做了。撤销最后一个选择,回到选择前的状态,去试另一个选择。

回溯的第一个要点:选择,以这些选择,展开了一颗空间树。

回溯的第二个要点:约束

本题有三个约束

  1. ip片段的值在0——255
  2. ip片段不能是0x和0xx
  3. IP片段的长度为1——3

根据这些约束条件,我们可以进行剪枝,避免继续搜索浪费时间空间

回溯的第三个要点:目标

  1. 目标决定了什么时候捕获答案,什么时候砍掉死支,回溯。
  2. 目标是生成 4 个有效片段,并且要耗尽 IP 的字符。
  3. 当满足该条件时,说明生成了一个有效组合,加入解集,结束当前递归,继续探索别的分支。
  4. 如果满4个有效片段,但没耗尽字符,不是想要的解,不继续往下递归,提前回溯。

设计dfs函数

  • 首先,我们为了确定下一次选择子串的初始位置我们需要传入start(初始位置的index),最初肯定是从0开始。再者,我们需要记录这条分支中已选择的字符串片段(ip片段),所以我们需要一个栈path记录。因此dfs有两个参数start和path
  • 然后我们得在dfs中检测该分支是否已获得到了目标答案,或者是已获取四个片段,但却没有耗尽s字符串(不符合要求),终止进行,回溯到上一选择,从path中删除最后一个选择,重新选择片段,符合条件的继续递归判断
  • 获取的字符串个数没有达到要求时,依次进行选择,不符合条件的剪枝,符合条件的继续递归进行选择判断

代码

/**
 * @param {string} s
 * @return {string[]}
 */
var restoreIpAddresses = function(s) {
    let res = [];
    // 从start处复原
    const dfs = function(start,path) {
        // 已从s中获得四个符合ip要求的字符串片段,且s中的字符都已使用,证明现在复原的ip地址符合要求,压入结果栈
        if(path.length === 4 && start === s.length) {
            res.push(path.join('.'));
            return;
        }
        // 已从s中获得四个符合ip要求的字符串片段,但s中的字符未使用完,不用继续进行,该选择分支不符合要求
        if(path.length === 4 && start < s.length){
            return;
        }
        // 根据ip地址的特性,每次取ip片段有三种选择
        for(let i = 1; i <= 3; i++){
            // 该种选择,获取字符串会越界,该分支不能继续
            if(start + i -1 >= s.length) return;
            // 该种选择字符串会是0x和0xx,不符合要求,该分支不能继续
            if (i != 1 && s[start] == '0') return;
            // 从s获取字符串 
            let str = s.substring(start, start + i);
            // 获取的字符串转成数字大于255,不符合要求,该分支不能继续
            if ( i == 3 && +str > 255) return;
            // 该字符串符合ip片段的要求,压入path中记录
            path.push(str);
            // 继续下一个ip片段选择
            dfs(start + i,path)
            // 上面一句的递归分支结束,撤销最后的选择,进入下一轮迭代,考察下一个切割长度
            path.pop();
        }
    }
    dfs(0,[]);
    return res;
};