leetcode93.复原IP地址:回溯算法中段控制与前导零处理的深度解析
一、题目深度解析与IP地址规则
题目描述
给定一个只包含数字的字符串s
,返回所有可能的有效IP地址组合。有效IP地址需满足以下条件:
- 由4个0-255的整数组成,用
.
分隔 - 每个整数不能以0开头(除非该整数本身是0)
- 例如输入
s="25525511135"
,输出["255.255.11.135","255.255.111.35"]
核心约束条件分析
- 段数固定:必须恰好分为4段,多一段或少一段均无效
- 数值范围:每段数值必须在0-255之间
- 前导零限制:以0开头的段只能是"0",不能是"01"、"023"等
- 长度限制:每段最多3个字符,IP地址总长度范围为4-12(4段*3字符+3个点)
二、回溯解法的核心实现与逻辑框架
完整回溯代码实现
class Solution {List<String> res = new ArrayList<>();StringBuilder temp = new StringBuilder();public List<String> restoreIpAddresses(String s) {// 预处理:长度不在4-12之间直接返回空if (s.length() < 4 || s.length() > 12) {return res;}backtracking(s, 0, 0, temp);return res;}public void backtracking(String s, int start, int cnt, StringBuilder temp) {// 终止条件:已分割4段if (cnt == 4) {// 若刚好分割完所有字符,添加到结果if (start == s.length()) {temp.setLength(temp.length() - 1); // 去掉最后一个多余的点res.add(new String(temp.toString()));}return;}int num = 0; // 当前段的数值for (int i = start; i < s.length() && i < start + 3; i++) { // 每段最多3个字符num = num * 10 + (s.charAt(i) - '0'); // 计算当前段数值// 剪枝条件:数值超过255 或 剩余字符过多(无法分成足够段)if (num > 255 || s.length() - i > 3 * (4 - cnt)) {break;}int len = temp.length(); // 记录当前长度用于回溯temp.append(num).append("."); // 添加当前段和分隔符backtracking(s, i + 1, cnt + 1, temp); // 递归处理下一段temp.setLength(len); // 回溯:恢复到添加前的状态if (s.charAt(start) - '0' == 0) { // 前导零处理:0开头的段只能有一个字符break;}}}
}
核心变量与参数解析:
res
:存储所有有效IP地址组合temp
:动态构建当前IP地址的字符串backtracking
参数:s
:输入的数字字符串start
:当前段的起始索引cnt
:已分割的段数temp
:当前构建的IP地址(含分隔符)
三、核心问题解析:段控制与前导零处理
1. 每段长度控制的实现
双重长度约束:
for (int i = start; i < s.length() && i < start + 3; i++)
- 当前段长度:通过
i < start + 3
限制每段最多3个字符 - 剩余长度预判:
s.length() - i > 3 * (4 - cnt)
- 推导:剩余字符数必须≤3*(剩余段数)
- 例:剩余2段时,剩余字符最多6个(3*2),若剩余7个字符则无法满足,提前剪枝
示例说明:
当s="123456789"
, cnt=2
(已分2段):
- 剩余段数=2,剩余字符数=9-已用段起始位置,若当前start=3,剩余字符6个≤3*2=6,合法
- 若start=2,剩余字符7个>6个,剪枝
2. 前导零的精准处理
核心逻辑:
if (s.charAt(start) - '0' == 0) {break;
}
- 条件解析:当段以0开头时(
s.charAt(start) == '0'
) - 处理方式:该段只能有一个字符(即"0"),break避免继续取后续字符
- 示例:处理"0123"时,第一段取"0"后break,避免生成"01.2.3.4"
前导零错误示例:
- 错误段:“01”、“023”、“00”
- 合法段:“0”、“10”、“255”
3. 递归逻辑的核心流程
回溯三步骤:
- 选择:从当前start位置取1-3个字符作为当前段
- 递归:处理下一段,段数cnt+1,起始位置i+1
- 回退:删除当前段和分隔符,尝试其他可能的段
代码体现:
temp.append(num).append("."); // 选择
backtracking(...); // 递归
temp.setLength(len); // 回退
四、回溯流程深度模拟:以输入"25525511135"为例
关键递归路径:
-
第一段处理(start=0, cnt=0):
- i=0: 取"2",temp=“2.”,递归start=1, cnt=1
- i=1: 取"25",temp=“25.”,递归start=2, cnt=1
- i=2: 取"255",temp=“255.”,递归start=3, cnt=1
-
第二段处理(以第一段"255"为例):
- start=3, cnt=1,s[3]=‘2’
- i=3: 取"2",temp=“255.2.”,递归start=4, cnt=2
- i=4: 取"25",temp=“255.25.”,递归start=5, cnt=2
- i=5: 取"255",temp=“255.255.”,递归start=6, cnt=2
-
第三段处理(以第二段"255"为例):
- start=6, cnt=2,s[6]=‘1’
- i=6: 取"1",temp=“255.255.1.”,递归start=7, cnt=3
- i=7: 取"11",temp=“255.255.11.”,递归start=8, cnt=3
- i=8: 取"111",temp=“255.255.111.”,递归start=9, cnt=3
-
第四段处理(以第三段"111"为例):
- start=9, cnt=3,s[9]=‘3’
- i=9: 取"3",temp=“255.255.111.3”,cnt=4但start=10≠s.length()=11,无效
- i=10: 取"35",temp=“255.255.111.35”,start=11=s.length(),有效,添加到结果
五、算法复杂度分析
1. 时间复杂度
- 理论上界:O(3^4 × n),每个段最多3种选择(1-3个字符),共4段,总组合数3^4=81,每次组合需O(n)构建字符串
- 实际复杂度:通过剪枝(数值范围、长度预判、前导零)大幅降低实际运行时间
2. 空间复杂度
- 递归栈:深度最大为4(段数),空间O(1)
- 结果集:最坏情况O(3^4 × n),每个IP长度15(3×4+3),空间O(1)
六、核心技术点总结:段控制的三大关键
1. 长度约束的双重剪枝
- 当前段长度:
i < start + 3
限制单段最大长度 - 剩余长度预判:
s.length() - i > 3*(4 - cnt)
避免无效搜索 - 数学意义:确保剩余字符足够分成剩余段数,且每段不超过3字符
2. 前导零的精准判断
- 判断时机:在取段的第一个字符时判断
- 处理逻辑:0开头的段只能有一个字符,避免生成非法段
- 代码实现:通过
if (s.charAt(start) == '0') break;
实现
3. 回溯状态的精准回退
- 状态记录:
int len = temp.length();
记录添加前的长度 - 回退操作:
temp.setLength(len);
一次性恢复到添加前的状态 - 避免错误:相比逐个删除字符,setLength更高效且不易出错
七、常见误区与优化建议
1. 前导零处理不完整
- 误区:仅判断段长度为1,未阻止0开头段取多个字符
if (num == 0 && i > start) break; // 错误,应在start位置判断
- 正确做法:在段起始位置判断是否为0,若是则break
2. 回溯状态恢复错误
- 误区:使用
deleteCharAt
逐个删除字符temp.deleteCharAt(temp.length() - 1); // 仅删除分隔符,未删除段字符
- 正确做法:记录添加前的长度,用
setLength(len)
整体恢复
3. 优化建议:预计算长度
if (s.length() < 4 || s.length() > 12) return res; // 提前过滤无效长度
- 作用:IP地址最短4字符(0.0.0.0),最长12字符(255.255.255.255)
- 效果:减少不必要的递归调用
八、总结:回溯算法中段控制的工程实践
本算法通过回溯法系统枚举所有可能的IP分段方式,核心在于:
- 段长度的双重控制:当前段长度与剩余长度预判结合,大幅减少无效搜索
- 前导零的精准处理:在段起始位置判断,确保段格式合法
- 状态的高效回退:通过记录长度实现状态的精准恢复
理解这种解法的关键是掌握递归过程中如何通过剪枝策略减少搜索空间,以及如何高效管理字符串状态。这种段控制方法在处理类似的分段问题(如合法IP生成、字符串分割)中具有广泛的应用价值,是回溯算法在工程实践中的典型应用。