leetcode回溯算法(93.复原IP地址)

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)

cpp 复制代码
if (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
cpp 复制代码
backtracking(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循环已经完全结束
}

.........

.........

.........

.........

相关推荐
Cx330❀2 小时前
【优选算法必刷100题】第038题(位运算):消失的两个数字
开发语言·c++·算法·leetcode·面试
燃于AC之乐2 小时前
我的算法修炼之路--5——专破“思维陷阱”,那些让你拍案叫绝的非常规秒解
c++·算法·贪心算法·bfs·二分答案·扩展域并查集·动态规划(最长上升子序列)
艾莉丝努力练剑2 小时前
【优选算法必刷100题】第021~22题(二分查找算法):山脉数组的峰顶索引、寻找峰值
数据结构·c++·算法·leetcode·stl
艾莉丝努力练剑2 小时前
【优选算法必刷100题】第007~008题(双指针算法):三数之和、四数之和问题求解
linux·算法·双指针·优选算法
gihigo19984 小时前
希尔伯特-黄变换(HHT)完整MATLAB实现
人工智能·算法·matlab
C++ 老炮儿的技术栈4 小时前
C/C++ 中 inline(内联函数)和宏定义(#define)的区别
开发语言·c++·git·算法·机器人·visual studio
大柏怎么被偷了4 小时前
【C++】哈希的应用
算法·哈希算法
血小板要健康4 小时前
如何计算时间复杂度(上)
java·数据结构·算法
古城小栈4 小时前
Rust Vec与HashMap全功能解析:定义、使用与进阶技巧
算法·rust