

startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。
本题我们还需要一个变量pointNum,记录添加逗点的数量。
cpp
vector<string> result;// 记录结果
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum) {
分割的段数作为终止条件。
pointNum表示逗点数量,pointNum为3说明字符串分成了4段了。
然后验证一下第四段是否合法,如果合法就加入到结果集里
cpp
if (pointNum == 3) { // 逗点数量为3时,分隔结束
// 判断第四段子字符串是否合法,如果合法就放进result中
if (isValid(s, startIndex, s.size() - 1)) {
result.push_back(s);
}
return;
}
在for (int i = startIndex; i < s.size(); i++)循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。
如果合法就在字符串后面加上符号.表示已经分割。
如果不合法就结束本层循环,如图中剪掉的分支:

递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.),同时记录分割符的数量pointNum 要 +1。
回溯的时候,就将刚刚加入的分隔符. 删掉就可以了,pointNum也要-1。
cpp
for (int i = startIndex; i < s.size(); i++) {
if (isValid(s, startIndex, i)) { // 判断 [startIndex,i] 这个区间的子串是否合法
s.insert(s.begin() + i + 1 , '.'); // 在i的后面插入一个逗点
pointNum++;
backtracking(s, i + 2, pointNum); // 插入逗点之后下一个子串的起始位置为i+2
pointNum--; // 回溯
s.erase(s.begin() + i + 1); // 回溯删掉逗点
} else break; // 不合法,直接结束本层循环
}
判断段位是否是有效段位了。
主要考虑到如下三点:
- 段位以0为开头的数字不合法
- 段位里有非正整数字符不合法
- 段位如果大于255了不合法
cpp
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
return false;
}
num = num * 10 + (s[i] - '0');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
处理字符串 "123"
初始: num = 0 处理 '1': num = 0*10 + (1) = 0 + 1 = 1 处理 '2': num = 1*10 + (2) = 10 + 2 = 12 处理 '3': num = 12*10 + (3) = 120 + 3 = 123
写法1:使用字符 '.'
cpp
s.insert(s.begin() + i + 1, '.');
// 在指定迭代器位置插入一个字符 '.'
// 参数:迭代器 + 字符
写法2:使用字符串 "."
cpp
s.insert(i + 1, ".");
// 在指定索引位置插入字符串 "."
// 参数:位置索引 + 字符串
两种写法最终得到的字符串是完全相同的!
int main() { string s1 = "25525511135"; string s2 = "25525511135"; // 写法1:使用字符 '.' s1.insert(s1.begin() + 3, '.'); cout << "写法1: " << s1 << endl; // 输出:255.25511135 // 写法2:使用字符串 "." s2.insert(3, "."); cout << "写法2: " << s2 << endl; // 输出:255.25511135 // 比较结果 cout << "两者是否相等: " << (s1 == s2) << endl; // 输出:1 (true) // 验证长度 cout << "s1长度: " << s1.length() << endl; // 输出:13 cout << "s2长度: " << s2.length() << endl; // 输出:13 return 0; }
break 的作用范围图示
backtracking(当前层递归)
├── for 循环开始
│ ├── i = startIndex → 检查 → 可能递归
│ ├── i = startIndex+1 → 检查 → 可能递归
│ ├── ...
│ └── 遇到不合法 → break → 跳出for循环
└── 函数结束,返回到上一层递归
// break: 只结束当前循环,继续执行循环后面的代码(如果有) for (...) { if (条件) { break; // 跳出循环 } } ...... ...... // 这里还会执行
// return: 结束整个函数,直接返回 void func() { for (...) { if (条件) { return; // 结束整个函数 } } ...... ...... // 如果return执行了,这里不会执行 }
完整代码
cpp
class Solution {
private:
vector<string> result; // 存储所有可能的有效 IP 地址
// 回溯函数
// s: 当前正在处理的字符串(会插入和删除 '.')
// startIndex: 当前搜索的起始位置
// pointNum: 已经添加的逗点(即 '.')的数量
void backtracking(string& s, int startIndex, int pointNum) {
// 当已经添加了 3 个点,说明字符串已经被分成了 4 段
if (pointNum == 3) {
// 判断最后一段(第四段)子字符串是否合法
if (isValid(s, startIndex, s.size() - 1)) {
// 如果合法,将当前完整的 IP 地址加入结果集
result.push_back(s);
}
return; // 结束当前递归分支
}
// 遍历可能的分割位置
for (int i = startIndex; i < s.size(); i++) {
// 判断从 startIndex 到 i 的子串是否是一个合法的 IP 段
if (isValid(s, startIndex, i)) {
// 在位置 i 后面插入一个点('.'),将当前段分隔开
s.insert(s.begin() + i + 1, '.');
pointNum++; // 点数加 1
// 递归处理下一段,注意起始位置是 i+2(因为插入了 '.')
backtracking(s, i + 2, pointNum);
// 回溯:撤销点的插入
pointNum--; // 点数减 1
s.erase(s.begin() + i + 1); // 删除插入的点
} else {
// 当前子串不合法,后面的分割只会更长,更不可能合法,所以直接跳出循环
break;
// break 语句结束的是for 循环,而不是结束整个递归函数
}
}
}
// 判断字符串 s 在区间 [start, end] 内的子串是否是一个合法的 IP 地址段
bool isValid(const string& s, int start, int end) {
// 如果区间无效,直接返回 false
if (start > end) {
return false;
}
// 如果子串以 '0' 开头,但长度大于 1(例如 "01"),则不合法
if (s[start] == '0' && start != end) {
return false;
}
int num = 0; // 用于计算子串对应的数值
for (int i = start; i <= end; i++) {
// 如果包含非数字字符,不合法
if (s[i] > '9' || s[i] < '0') {
return false;
}
// 逐位计算数值
num = num * 10 + (s[i] - '0');
// 如果数值大于 255,不合法
if (num > 255) {
return false;
}
}
// 通过所有检查,返回合法
return true;
}
public:
vector<string> restoreIpAddresses(string s) {
result.clear(); // 清空结果集,确保每次调用都是干净的
// 剪枝:IP 地址长度必须在 4 到 12 之间(每个段至少 1 位,最多 3 位)
if (s.size() < 4 || s.size() > 12) {
return result;
}
// 从字符串开头开始回溯,初始点数为 0
backtracking(s, 0, 0);
// 返回所有可能的 IP 地址
return result;
}
};
if (s.size() < 4 || s.size() > 12) return result; if语句进行长度检查,判断结果为false,所以不用return,继续下一行代码 backtracking(s, 0, 0);
调用backtracking(s, 0, 0)
状态: s = "25525511135", startIndex=0, pointNum=0 if条件不满足,接着for循环 循环 i=0:
cpp// 遍历可能的分割位置 for (int i = startIndex; i < s.size(); i++) { // 判断从 startIndex 到 i 的子串是否是一个合法的 IP 段 if (isValid(s, startIndex, i)) { // 在位置 i 后面插入一个点('.'),将当前段分隔开 s.insert(s.begin() + i + 1, '.'); pointNum++; // 点数加 1 // 递归处理下一段,注意起始位置是 i+2(因为插入了 '.') backtracking(s, i + 2, pointNum); // 回溯:撤销点的插入 pointNum--; // 点数减 1 s.erase(s.begin() + i + 1); // 删除插入的点 } else { // 当前子串不合法,后面的分割只会更长,更不可能合法,所以直接跳出循环 break; } }
1. isValid(s, 0, 0) → "2" 合法 (0-255, 无前导零) 2. 插入点: s = "2.5525511135" 3. pointNum = 1 4. 递归: backtracking("2.5525511135", 2, 1)
调用backtracking("2.5525511135", 2, 1)
状态: s = "2.5525511135", startIndex=2, pointNum=1 if条件不满足,接着for循环 循环 i=2: 1. isValid(s, 2, 2) → "5" 合法 2. 插入点: s = "2.5.525511135" 3. pointNum = 2 4. 递归: backtracking("2.5.525511135", 4, 2)
调用backtracking("2.5.525511135", 4, 2)
状态: s = "2.5.525511135", startIndex=4, pointNum=2 if条件不满足,接着for循环 循环 i=4: 1. isValid(s, 4, 4) → "5" 合法 2. 插入点: s = "2.5.5.25511135" 3. pointNum = 3 4. 递归: backtracking("2.5.5.25511135", 6, 3)
调用backtracking("2.5.5.25511135", 6, 3)
cppif (pointNum == 3) { // 条件成立 if (isValid(s, startIndex, s.size() - 1)) { // isValid("2.5.5.25511135", 6, 12) // 检查 "25511135" → 数值 25511135 > 255 → 不合法 // 不加入结果 // 如果合法,将当前完整的 IP 地址加入结果集 result.push_back(s); } return; // 返回 }
满足if条件:pointNum == 3 接着检查第四段的数值是否满足IP地址的条件,数值 25511135 > 255 → 不合法 所以直接return
继续回到原来调用的backtracking("2.5.525511135", 4, 2)
之前的状态s = "2.5.5.25511135" (注意:还有点在字符串中) - startIndex = 4 - pointNum = 3 - 循环变量 i = 4
cppbacktracking(s, i + 2, pointNum); // 回溯:撤销点的插入 pointNum--; // 点数减 1 s.erase(s.begin() + i + 1); // 删除插入的点
执行回溯操作后,pointNum 从 3 变为 2 s.erase(s.begin() + i + 1); // 删除索引 5 的点(因为 i=4)
所以现在的新状态: - s = "2.5.525511135" - pointNum = 2 接着循环继续:i++ → i = 5
for (int i = startIndex; i < s.size(); i++) { // 当前 i = 5, startIndex = 4 if (isValid(s, startIndex, i)) { // isValid("2.5.525511135", 4, 5) // 检查子串 s[4..5] = "52" 是否合法 // "52" 在 0-255 之间,无前导零 → 合法 s.insert(s.begin() + i + 1, '.'); // i = 5 ,在索引 6 处插入点 //执行后:s = "2.5.52.5511135" pointNum++; // 从 2 变为 3 backtracking(s, i + 2, pointNum); // backtracking("2.5.52.5511135", 7, 3) pointNum--; s.erase(s.begin() + i + 1); } else break; }
调用backtracking("2.5.52.5511135", 7, 3)
void backtracking(string& s, int startIndex, int pointNum) { if (pointNum == 3) { // if条件成立 // 判断第四段:s[7..end] = "5511135" if (isValid(s, startIndex, s.size() - 1)) { // isValid(..., 7, 12) // 检查 "5511135" // 计算:5→55→551→5511→55111→551113→5511135 > 255 // 在 num = 551 时就已经 > 255,返回 false result.push_back(s); // 不执行 } return; // 返回 } 不会执行下面的循环 }
返回上一层
// 从递归返回后执行回溯 pointNum--; // 从 3 变回 2 s.erase(s.begin() + i + 1); // i=5,删除索引 6 的点 现在状态: - s = "2.5.525511135" - pointNum = 2 - 循环继续:i++ → i = 6
// i = 6, startIndex = 4 if (isValid(s, startIndex, i)) { // isValid("2.5.525511135", 4, 6) // 检查子串 s[4..6] = "525" // 计算:5→52→525 > 255 → 不合法 // 返回 false } else { break; // 跳出循环,这一层的for循环已经完全结束 }
.........
.........
.........
.........