← 返回题目列表

复原 IP 地址如何用回溯处理分段、前导零和范围校验?

高频 中等 第 4 / 30 题 更新于 2026/07/30
回溯字符串IP地址剪枝

简化版

复原 IP 地址就是把字符串切成 4 段,每段长度 1 到 3,数值必须在 0 到 255,且不能有非法前导零。回溯状态记录当前下标和已切出的段,段数为 4 且正好用完字符串时收集答案。

详细版

核心状态是 dfs(index, parts)index 表示下一段从字符串哪里开始,parts 保存已切出的 IP 段。每一层尝试长度 1..3 的子串,若子串越界、前导零非法、数值大于 255,就跳过或停止。

剪枝可以从剩余长度入手:还需要 remainParts = 4 - parts.size() 段,每段至少 1 位、最多 3 位,所以剩余字符数必须满足 remainParts <= remainingChars <= 3 * remainParts。不满足时直接返回。

复杂度很小,因为 IP 固定只有 4 段,每段最多 3 种长度,最多尝试 3^4 = 81 条切分路径。

完整版教学

一、题目本质是“切分型回溯”

输入 s = "25525511135",输出可以是:

255.255.11.135
255.255.111.35

这类题不是在每个字符上选或不选,而是在当前位置决定“下一刀切多长”。IP 地址固定 4 段,每段长度只能是 1、2、3,因此搜索树非常窄。

index=0
├─ 取 1 位
├─ 取 2 位
└─ 取 3 位

二、合法 IP 段有三个条件

每一段必须同时满足:

条件说明示例
长度 1 到 3IPv4 每段最多三位十进制数125255
数值 0 到 255超过 255 非法256 非法
无非法前导零长度大于 1 时不能以 0 开头0 合法,01 非法

前导零是面试最容易漏的点。"010010" 的合法结果中可以有 0.10.0.10,但不能有 0.100.1.0 之外的 01 这种段。

记忆钩子:IP 段先看长度,再看前导零,最后看数值;三个门都过了才递归。

三、状态设计:index 和 parts

回溯函数可以写成 dfs(index, parts)

index:下一段从 s[index] 开始切
parts:已经切出的段,例如 ["255", "255"]

终止条件有两个维度:段数和字符是否用完。只有 parts.size() == 4 && index == s.length() 才是合法答案;段数到 4 但字符串没用完,或者字符串用完但段数不足 4,都不合法。

四、剩余长度剪枝怎么推

假设还需要 remainParts 段,剩余字符数是 remainingChars。每段至少 1 个字符,所以 remainingChars 不能小于 remainParts;每段最多 3 个字符,所以不能大于 3 * remainParts

remainParts <= remainingChars <= 3 * remainParts

例如字符串剩余 7 个字符,但只剩 2 段可切,最多能容纳 6 个字符,当前分支必然失败。这个剪枝可以放在每次进入递归函数的开头。

五、代码模板

List<String> restoreIpAddresses(String s) {
    List<String> res = new ArrayList<>();
    dfs(s, 0, new ArrayList<>(), res);
    return res;
}

void dfs(String s, int index, List<String> parts, List<String> res) {
    int remainParts = 4 - parts.size();
    int remainingChars = s.length() - index;
    if (remainingChars < remainParts || remainingChars > remainParts * 3) return;

    if (parts.size() == 4) {
        if (index == s.length()) res.add(String.join(".", parts));
        return;
    }

    for (int len = 1; len <= 3 && index + len <= s.length(); len++) {
        String part = s.substring(index, index + len);
        if (part.length() > 1 && part.charAt(0) == '0') break;
        int value = Integer.parseInt(part);
        if (value > 255) break;
        parts.add(part);
        dfs(s, index + len, parts, res);
        parts.remove(parts.size() - 1);
    }
}

这里两个 break 都有依据:长度继续增加时,前导零问题不会消失;数值超过 255 后,再取更长只会更大。

六、用样例走一遍

s = "101023" 为例:

1 | 0 | 10 | 23  -> 1.0.10.23
1 | 0 | 102 | 3  -> 1.0.102.3
10 | 1 | 0 | 23  -> 10.1.0.23
10 | 10 | 2 | 3  -> 10.10.2.3
101 | 0 | 2 | 3  -> 101.0.2.3

1 | 010 | 2 | 3 会因为 010 有前导零被剪掉;像 101 | 023 这种段同样非法。

七、常见误区与追问

  • 误区:只判断数值小于 255。 01 的数值是 1,但作为 IP 段非法。
  • 误区:段数到 4 就直接收集。 必须同时满足字符串刚好用完。
  • 误区:字符串用完就直接收集。 还要检查是否已经切出 4 段。
  • 追问:为什么复杂度可以看作常数? 因为 IPv4 固定 4 段,每段最多尝试 3 种长度,搜索上限很小。
  • 追问:剪枝条件怎么记? 剩余字符必须能被剩余段数装下,即每段 1 到 3 位。
  • 追问:为什么 value > 255 后可以 break? 当前位数继续增加只会让十进制数更大,不可能重新合法。

八、加强记忆

复原 IP 地址要记成“固定 4 段的切分回溯”:每层不是选择某个元素,而是选择下一段切 1、2、3 位。判断合法段按“长度、前导零、数值范围”三步走,终止条件按“4 段且刚好用完”双条件判断,剪枝按剩余字符能否放进剩余段数来推。这样答题时既能写出代码,也能解释每个 returnbreak 的原因。