leetcode刷题日记-【93. 复原 IP 地址】

217 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情

题目描述

有效 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 = "101023" 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]  

提示:

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

题目元素

  • 给定一个只包含数字的字符串s,返回将这个字符串用'.'分割,得到的所有有效的ip地址;
  • 有效ip,总共4个段,每个段在[0,255]之间,且数字不能以0开头(除0本身之外)。

解题思路

首先,确认这个字符串最多只能被分割成4个,且每个子串的最大长度为3,所以一些前置条件,当s长度大于12时或者s长度小于4时,直接返回空数组。

解题思路就是移动'.'的位置,然后判断获得的新ip地址是否合法;在一定范围内移动某一元素可以使用回溯+剪枝的方法。

首先确定本题回溯的三要素:

  • 已选择路径,一条路径中已经分割完的子串;
  • 可做出的选择,还未被放入路径中的s中的元素;
  • 终止条件:当可做出的选择长度为0并且当前已做出的选择长度为4。

剪枝:

什么是剪枝呢?就是优化搜索过程中不必要的路径,缩减路径范围,达到优化的目的的一种手段。

  • 数字第一位为0,则当前子串不能再追加数字;
  • 数字长度为3,且大小大于255,直接进行下次循环。

代码实现

public static List<String> restoreIpAddresses(String s) {
    List<String> res = new ArrayList<>();
    char[] chars = s.toCharArray();
    if (chars.length <4 || chars.length > 12) {
        return res;
    }
    // 已选择的ip子串,长度为4
    int[] path = new int[4];
    // 回溯
    backTrack(chars,0,0,path,res);
    return res;
}

/**
 * 回溯主方法
 *
 * @param chars 所有字符
 * @param index 当前选择到的chars的索引,可选择的路径为[index,chars.length-1]
 * @param num 当前ip段已选择到的段数
 * @param path 已选择的路径
 * @param res 所有结果
 */
private static void backTrack(char[] chars, int index, int num, int[] path, List<String> res) {
    if (index == chars.length) {
        // 终止条件
        if (num == 4) {
            // 将当前选择放入res中
            StringBuffer sb = new StringBuffer();
            for (int i : path) {
                sb.append(i).append(".");
            }
            res.add(sb.substring(0, sb.length() - 1));
        }
        return;
    }
    if (num == 4) {
        return;
    }
    // 做选择
    char nowChar = chars[index];
    // 加入当前组
    if (nowChar == '0') {
        // 直接放入当前分组
        path[num] = 0;
        // 进行下一次选择
        backTrack(chars,index+1,num+1,path,res);
        return;
    }
    // 当前选择的段值
    int nowSeg = 0;
    // 循环当前段可做的选择
    for (int i=index;i<chars.length&&i<index+3;i++) {
        nowSeg = nowSeg * 10 + (chars[i]-'0');
        if (nowSeg > 255) {
            break;
        }
        // 做选择
        // 循环里面每次选择都会覆盖之前做的选择,所以也算是回退了
        path[num] = nowSeg;
        backTrack(chars,i+1,num+1,path,res);
    }
}