LeetCode 93. 复原 IP 地址
题目描述
考察公司:作业帮
考察时间:2025.5.27

题意分析
输入一个只含数字的字符串,要求在其中插入三个点,把它切成四段,输出所有能构成合法 IPv4 地址的切法。字符串本身不能重排、不能增删字符,只能决定「在哪里切」。
「合法」由三条规则共同定义,缺一条都会做错:
- 数值范围:每段解释成十进制数后必须落在 0 到 255 之间;
- 无前导零:除了单独的
"0",任何段都不能以 0 开头,"01"、"00"、"010"全部非法;- 恰好四段且用完全部字符:不能少切、不能多切,也不能剩下任何字符没有归入某一段。
约束透露的信号很强:每段长度只能是 1 到 3,四段加起来总长只能落在 4 到 12 之间,所以有效输入的规模天然被钉死在一个极小的区间里;而题目要的是「所有可能的结果」,不是判断存在性,也不是求最优解——这意味着必须把每一种切法都考虑到,漏一种就是错。
需要提前想清楚的边界:长度小于 4 或大于 12 的输入直接无解;
"0000"有唯一答案"0.0.0.0",说明前导零规则不能把单独的"0"一起拒掉;"010010"这类含 0 的输入是前导零规则的试金石;可能存在合法切法为零的输入,此时应返回空列表而不是报错。
解法:回溯枚举每段
核心思路
从左到右依次确定 4 个 IP 段,每段只尝试 1 到 3 位。选择前检查前导零和数值上限,并根据“剩余字符是否能装入剩余段”提前剪枝;凑满 4 段且恰好用完字符串时收集答案。
解题步骤
- 递归状态记录当前下标和已选段。
- 若剩余字符少于剩余段数,或多于其 3 倍,直接返回。
- 当前段逐位构造数值;出现前导零或数值超过
255时停止扩展。- 选择当前段后递归,返回时撤销;4 段正好用完字符时记录结果。
代码实现
class Solution {
public List<String> restoreIpAddresses(String s) {
List<String> answer = new ArrayList<>();
backtrack(s, 0, new ArrayList<>(), answer);
return answer;
}
private void backtrack(String s, int start, List<String> parts, List<String> answer) {
if (parts.size() == 4) {
if (start == s.length()) {
answer.add(String.join(".", parts));
}
return;
}
int chars = s.length() - start;
int segments = 4 - parts.size();
if (chars < segments || chars > segments * 3) {
return;
}
int value = 0;
for (int end = start; end < s.length() && end < start + 3; end++) {
if (end > start && s.charAt(start) == '0') {
break;
}
value = value * 10 + s.charAt(end) - '0';
if (value > 255) {
break;
}
parts.add(s.substring(start, end + 1));
backtrack(s, end + 1, parts, answer);
parts.remove(parts.size() - 1);
}
}
}
func restoreIpAddresses(s string) []string {
answer := make([]string, 0)
parts := make([]string, 0, 4)
var backtrack func(int)
backtrack = func(start int) {
if len(parts) == 4 {
if start == len(s) {
answer = append(answer, parts[0]+"."+parts[1]+"."+parts[2]+"."+parts[3])
}
return
}
chars, segments := len(s)-start, 4-len(parts)
if chars < segments || chars > segments*3 {
return
}
value := 0
for end := start; end < len(s) && end < start+3; end++ {
if end > start && s[start] == '0' {
break
}
value = value*10 + int(s[end]-'0')
if value > 255 {
break
}
parts = append(parts, s[start:end+1])
backtrack(end + 1)
parts = parts[:len(parts)-1]
}
}
backtrack(0)
return answer
}
复杂度分析
- 时间复杂度:$O(1)$,固定 4 层、每层最多 3 个选择,搜索规模至多为常数级。
- 空间复杂度:$O(1)$,递归深度和路径长度都固定为 4;不计返回结果。
关键点总结
- 合法段的长度为 1 到 3,数值范围为
0到255。- 单独的
"0"合法,多位段不能以0开头。- 剩余字符必须满足
segments <= chars <= 3 * segments。- 收集答案时既要凑满 4 段,也要恰好用完所有字符。
易错点总结
- 只判断数值会误收
"01"、"00"等带前导零的段。- 凑满 4 段后若不检查字符串是否用完,会产生残缺结果。
- 递归返回后必须撤销当前段,避免不同分支互相污染。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| LCR 087. 复原 IP 地址 | 中等 | 与本题完全同题,适合换一门语言或换一种写法自测 |
| 131. 分割回文串 | 中等 | 同为切分字符串的回溯,但段数不固定,合法性判据换成回文 |
| 306. 累加数 | 中等 | 只需枚举前两段、其余由加法递推唯一确定,前导零规则与本题一致 |
| 468. 验证IP地址 | 中等 | 不做搜索只做校验,还要同时兼容 IPv6,考规则细节的完备性 |
| 140. 单词拆分 II | 困难 | 切分方案数不再有常数上界,回溯之上还要靠记忆化控制爆炸 |
| 751. IP 到 CIDR | 中等 | 反向问题:从起始 IP 生成网段,考 IP 与整数互转及位运算 |