LeetCode LCR 087. 复原 IP 地址
题目描述


题意分析
在数字字符串中插入三个点,得到恰好四段的 IPv4 地址。所有字符必须按原顺序用完,每段数值在
0到255之间,只有单独的零允许以零开头。每个合法段只能有一到三位,因此可以从左到右枚举每段长度。四段总共只能覆盖四到十二个字符,范围之外一定无解;官方题面允许空串以及最长 3000 个字符的输入,不能假定字符串本身已经适合构成地址。
解法:按四段约束回溯
核心思路
[!blue]
dfs(i)表示s[0..i-1]已经切成t中的若干合法段,下一段从i开始。始终保持各段拼接后恰好覆盖此前的字符,递归到j+1才能让下一段与当前段相邻而不重叠。成功必须同时满足字符用完和已有四段。只用完字符但段数不足,或已有四段却还剩字符,都不能形成地址,应直接返回。代码先检查成功,再检查这两类无法继续的状态,因此不会把完整答案提前拦掉。
从起点
i开始最多取三位作为当前段,段尾j不能越过串尾。x保存s[i..j]的数值,每延长一位就执行x = x*10 + digit,可以增量判断数值范围。若
x > 255,继续追加数字只会使数值更大;若首字符为零且段长已超过一,再延长仍然带前导零。这两种失败都不可能通过延长修复,所以用break排除后面所有更长候选;单独的零仍能进入递归。每次只选合法段,所以成功分支一定是有效地址;任意有效地址的各段长度都在枚举范围内,也不会被上述剪枝排除,因此不会漏解。不同分段边界确定不同的点号位置,不会重复生成同一个地址。成功时拼接为新字符串,回溯撤销最后一段不会改变已保存的结果。
解题步骤
- 从起点零和空段列表开始递归。
- 字符已用完且恰好四段时,用点号连接各段并保存;只满足其中一项时返回。
- 从当前起点尝试一到三位,增量计算段值,遇到超限或多位前导零就停止延长。
- 将合法段加入路径,递归处理后缀,再撤销这一段。
代码实现
class Solution {
private int n;
private String s;
private List<String> answer = new ArrayList<>();
private List<String> t = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
n = s.length();
this.s = s;
dfs(0);
return answer;
}
// i:当前待切分段的起点,s[0..i-1] 已切成 t 中的若干合法段。
private void dfs(int i) {
// 字符用完且恰好四段,才是答案。
if (i >= n && t.size() == 4) {
answer.add(String.join(".", t));
return;
}
// 字符用完但段数不足,或段数已满但字符没用完,都无解。
if (i >= n || t.size() >= 4) {
return;
}
int x = 0;
// 段长最多 3,同时不越界。
for (int j = i; j < Math.min(i + 3, n); ++j) {
x = x * 10 + s.charAt(j) - '0';
// 超过 255 或出现前导零后,继续延长只会更糟,直接 break。
if (x > 255 || (s.charAt(i) == '0' && i != j)) {
break;
}
t.add(s.substring(i, j + 1));
dfs(j + 1);
t.remove(t.size() - 1);
}
}
}
import (
"strings"
)
func restoreIpAddresses(s string) (answer []string) {
n := len(s)
t := []string{}
// i:当前待切分段的起点,s[0..i-1] 已切成 t 中的若干合法段。
var dfs func(int)
dfs = func(i int) {
// 字符用完且恰好四段,才是答案。
if i >= n && len(t) == 4 {
answer = append(answer, strings.Join(t, "."))
return
}
// 字符用完但段数不足,或段数已满但字符没用完,都无解。
if i >= n || len(t) == 4 {
return
}
x := 0
// 段长最多 3,同时不越界。
for j := i; j < i+3 && j < n; j++ {
x = x*10 + int(s[j]-'0')
// 超过 255 或出现前导零后,继续延长只会更糟,直接 break。
if x > 255 || (j > i && s[i] == '0') {
break
}
t = append(t, s[i:j+1])
dfs(j + 1)
t = t[:len(t)-1]
}
}
dfs(0)
return
}
复杂度分析
- 时间复杂度:$O(1)$。最多选择四段,每段至多尝试三种长度,搜索树规模受 $3^4$ 限制;每个结果最多包含十二位数字和三个点。即使输入很长,代码也只搜索有限的前缀,四段后仍有字符就返回。
- 空间复杂度:不计输出为 $O(1)$。递归深度最多五层,路径最多保存四个长度不超过三的段。
关键点总结
[!green]
- 段值合法、没有多位前导零、恰好四段、原字符串用完,四项条件缺一不可。
- 数值超限和前导零都会随延长持续失效,因此可以直接结束本层的后续枚举。
- 长度不在四到十二之间时没有答案,现有段长和段数限制也会自然返回空结果,不需要扫描完整长串。
易错点总结
[!yellow]
- 必须恰好四段且全部字符用完,不能只检查其中一个条件。
- 单独的 0 合法,01、00 这样的多位前导零不合法。
- 每段最多三位且不超过 255,越界前停止扩展,回溯后删除最后一段。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 468. 验证IP地址 | 中等 | 原题验证一个IP地址,本题枚举分段后也必须检查每段范围与前导零。 |
| 131. 分割回文串 | 中等 | 同样按子串分段回溯,原题每段需回文,本题必须四段且每段满足IPv4规则。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!