1题目
有效 IP 地址 正好由四个整数(每个整数位于 0
到 255
之间组成,且不能含有前导 0
),整数之间用 '.'
分隔。
- 例如:
"0.1.2.201"
和"192.168.1.1"
是 有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。
给定一个只包含数字的字符串 s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s
中插入 '.'
来形成。你 不能 重新排序或删除 s
中的任何数字。你可以按 任何 顺序返回答案。
示例 1:
输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000" 输出:["0.0.0.0"]
示例 3:
输入:s = "101023" 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
2链接
题目链接:93. 复原 IP 地址 - 力扣(LeetCode)
视频链接:回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址_哔哩哔哩_bilibili
3解题思路
又是回溯,我感觉好无力啊...
切割问题可以抽象为树型结构,如图:
我踏马直接回溯三部曲:
1、确定函数参数及返回值
不能重复切割,所以需要startIndex来控制;需要切割四段,所以需要pointNum来记录点的数量
vector<string> result;// 记录结果
// startIndex: 搜索的起始位置,pointNum:添加逗点的数量
void backtracking(string& s, int startIndex, int pointNum) {
2、确定终止条件
终止条件和131.分割回文串 (opens new window)情况就不同了,本题明确要求只会分成4段,所以不能用切割线切到最后作为终止条件,而是分割的段数作为终止条件。
pointNum表示点数量,pointNum为3说明字符串分成了4段了。然后验证一下第四段是否合法,如果合法就加入到结果集里
if (pointNum == 3) { // 逗点数量为3时,分隔结束
// 判断第四段子字符串是否合法,如果合法就放进result中
if (isValid(s, startIndex, s.size() - 1)) {
result.push_back(s);
}
return;
}
3、单层递归逻辑
在for (int i = startIndex; i < s.size(); i++)
循环中 [startIndex, i] 这个区间就是截取的子串,需要判断这个子串是否合法。
如果合法就在字符串后面加上符号.
表示已经分割。
如果不合法就结束本层循环,如图中剪掉的分支:
递归调用时,下一层递归的startIndex要从i+2开始(因为需要在字符串中加入了分隔符.
),同时记录分割符的数量pointNum 要 +1。
回溯的时候,就将刚刚加入的分隔符.
删掉就可以了,pointNum也要-1。
注意C++在使用insert函数插入'.'的时候,要在pos位置前插入,可能与其他语言不同。
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了不合法
// 判断字符串s在左闭又闭区间[start, end]所组成的数字是否合法
bool isValid(const string& s, int start, int end) {
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
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');
if (num > 255) { // 如果大于255了不合法
return false;
}
}
return true;
}
4代码
class Solution {
private:
vector<string> result; //收集结果
bool isValid(string& s, int start, int end) {
if (start > end) return false;
// 在不是仅一个数字时,以0开头不合法
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;//检查非法字符
//计算本段s转换为数字的值,注意ASCII字符相减得到的是整数的差
num = num * 10 + (s[i] - '0');
if (num > 255) return false;//大于255,非法
}
return true;
}
void backtracking(string& s, int startIndex, int piontNum) {
if (piontNum == 3) {//点数量为3时,分隔结束
// 判断第四段子字符串是否合法,如果合法就放进result中
if (isValid(s, startIndex, s.size() - 1)) {
result.push_back(s);
}
}
for (int i = startIndex; i < s.size(); i++) {
// 判断 [startIndex,i] 这个区间的子串是否合法
if (isValid(s, startIndex, i)) {
s.insert(s.begin() + i + 1, '.');//C++的insert是在pos的前面插入
piontNum++;
backtracking(s, i + 2, piontNum);//插入点之后下一个子串的起始位置为i+2
piontNum--;//回溯
s.erase(s.begin() + i + 1);//回溯
}
else break;
}
}
public:
vector<string> restoreIpAddresses(string s) {
if (s.size() < 4 || s.size() > 12) return result;
backtracking(s, 0, 0);
return result;
}
};