代码随想录算法训练营第21天|93.复原IP地址、78. 子集、90.子集II
LeetCode93
2025-02-21 17:00:41 星期五
题目描述:力扣93
文档讲解:代码随想录(programmercarl)93.复原IP地址
视频讲解:《代码随想录》算法视频公开课:回溯算法如何分割字符串并判断是合法IP?| LeetCode:93.复原IP地址
代码随想录视频内容简记

要点1
关于如何在结果集添加最后的IP地址,这个我一开始写的还是一个result和一个path,但是发现这里就出问题了。定义的path是string类型的,但是string没法push_back的
string str = s.substr(index, i - index + 1);
path.push_back(str);
后面看了k哥的视频,这个不需要定义path,在终止的时候直接添加的是s,因为会在每次递归之前就把字符串修改好,关键还是要根据力扣给出的输出实例具体分析

要点2
本题的IP地址有效必须满足一个条件,那就是一共四个数,三个句点。这里要把句点数量当作参数传进去。
本题的第一感觉就是细节很多,自己先试着做了一下,不好想的
梳理
-
确定函数的参数和返回值
-
确定递归的终止条件
-
确定单层搜索的逻辑
大致代码内容
-
void backtracking (string s, int index, int pointSum)
这里需要用到pointSum控制句点的数量 -
终止条件就是
pointSum == 3
,另外因为一旦添加了第三个句点后,进入递归,直接判断最后的字符串是否有效即可push_back
。因为四个整数的条件限制不能再进入递归了就 -
确定单层搜索的逻辑,这里就是需要在for循环中每次进入递归之前判断字符串是否有效。
判断有效有三个点:-
首位不能为0,否则false
-
字符串的值不能大于255,否则false
-
不能有非整数字符,否则false
-
LeetCode测试
这个题好恶心😭😭,折腾了半天就是在isValid函数上面
坑1——整数溢出
刚开始写的这样的,发现总会报整数溢出
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') return false;
// 这里针对大于255做判断
num = num * 10 + (s[i] - '0');
}
if (num > 255) return false;

看了一下,举个例子把,刚开始分割的时候,比如25525511135这个字符串,第一次切到了这里,2|5525511135,之后的[index,i]区间内,他会逐个遍历,递归,回溯,直到[index, s.size() - 1],这时就会溢出
所以,避免这种可以在他*10之前进行判断,也就是放到里面,我看k哥是这么写的
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') return false;
// 这里针对大于255做判断
num = num * 10 + (s[i] - '0');
if (num > 255) return false;
}
坑2——关于首位是0的判断
一开始写的,if (end - start > 1 && s[start] == '0') return false;
,一直没有看出来哪里有错,打印了一下
cout << "end:" << end << endl;
cout << "start:" << start << endl;
cout << s.substr(start, end - start + 1) << endl;
cout << s[start] << endl;

可以看到,end和start相差为1的时候,其实是有两位的,我没注意到。所以应该是end - start >= 1
坑3——关于start>end的判断
其实感觉这个判断就很奇怪,正常来讲是不应该出现这种情况的,怎么会start比end大呢?而且一不加上这个,最后的结果就会出错,太奇怪了。这个问题也琢磨了很久
然后我把异常打印了出一下

可以看到的是,异常是出现在index = 9,i = 8
,一开始感觉很震惊,就把s字符串也顺便打印出来才明白的。出现异常的上一个index = 7, i = 7
,其字符串是10.102.3,此时的index = 7正好指向末尾的3,在for循环中i初始为7,接着进入有效性判断中,isValid(s, 7, 7)
,一位数字3,没有问题,有效。那么接着就会插入一个句点,'.'变成10.102.3.。进入递归,注意,现在的i = 7。backtracking(s, 9, 3)
,
for (int i = index; i < s.size(); i++) {
if (isValid(s, index, i)) {
s.insert(s.begin() + i + 1, '.');
pointSum++;
} else continue;
backtracking(s, i + 2, pointSum);
s.erase(s.begin() + i + 1);
pointSum--;
}
此时又会进入有效性判断,就会出现这个isValid(s, 9, 8)
,也就是这里之所以会出现这种情况,是代码本身的+2决定的,8也只是s.size() - 1
后的结果,并不是i的值,感觉这里需要注意。
if (pointSum == 3) {
if (isValid(s, index, s.size() - 1)) {
result.push_back(s);
return;
}
}
之后,递归的结束,erase操作,会把第8位删除掉,也就是刚刚添加的'.',之后pointSum--恢复为8,for循环结束,之后,更上一层的递归结束,开始回溯......
也就是说,这里的if (start > end) return false
是有特定意义的,代码的逻辑不变,这里就必须得加上
完整代码如下
这个题写的我整个人都不好了😵
点击查看代码
class Solution {
private:
bool isValid(string s, int start, int end) {
cout << "end:" << end << endl;
cout << "start:" << start << endl;
cout << "substr" << s.substr(start, end - start + 1) << endl;
cout << "s[start]" << s[start] << endl;
cout << s << endl;
cout << endl;
if (start > end) return false;
if (end - start >= 1 && s[start] == '0') return false;
int num = 0;
for (int i = start; i <= end; i++) {
if (s[i] > '9' || s[i] < '0') return false;
// 这里针对大于255做判断
num = num * 10 + (s[i] - '0');
if (num > 255) return false;
}
return true;
}
vector<string> result;
string path;
void backtracking (string s, int index, int pointSum) {
if (pointSum == 3) {
if (isValid(s, index, s.size() - 1)) {
result.push_back(s);
return;
}
}
for (int i = index; i < s.size(); i++) {
if (isValid(s, index, i)) {
s.insert(s.begin() + i + 1, '.');
pointSum++;
} else continue;
backtracking(s, i + 2, pointSum);
s.erase(s.begin() + i + 1);
pointSum--;
}
}
public:
vector<string> restoreIpAddresses(string s) {
backtracking(s, 0, 0);
return result;
}
};
LeetCode78
题目描述:力扣78
文档讲解:代码随想录(programmercarl)78.子集
视频讲解:《代码随想录》算法视频公开课:回溯算法解决子集问题,树上节点都是目标集和! | LeetCode:78.子集
代码随想录视频内容简记
需要注意的是,子集问题的结果集是存在于每一个结点的,包括根节点。我们在存放结果的时候,和之前的处理方式有不同,if (index == nums.size())
表示的index不断取到直到等于集合的数量大小,但是这么做只能取到叶子结点。所以,push操作要和if判断分开

梳理
-
确定函数的参数和返回值
-
确定递归的终止条件
-
确定单层递归的逻辑
大致代码内容
-
result.push_back(path)
,if (index == nums.size()) return
,之所以push在if的前面,是因为要在最后index = 2
指向3,加1之后变为3进入递归后,能把这次的子集添加进去,再返回 -
但层递归的逻辑和之前一样
大致代码内容
点击查看代码
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking (vector<int>& nums, int index) {
result.push_back(path);
if (index == nums.size()) return;
for (int i = index; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
};
LeetCode90
题目描述:力扣90
文档讲解:代码随想录(programmercarl)90.子集II
视频讲解:《代码随想录》算法视频公开课:回溯算法解决子集问题,如何去重?| LeetCode:90.子集II
要点
-
核心的还是去重,对used数组的使用,注意这个if条件别写错了,一开始把
nums[i] == nums[i - 1]
写成used[i] != used[i - 1]
了,不要写错了if (i >= 1 && nums[i] == nums[i - 1] && used[i - 1] == false)
-
注意对数组进行排序,用sort即可
LeetCode测试
点击查看代码
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int index, vector<bool>& used) {
result.push_back(path);
if (index == nums.size()) return;
for (int i = index; i < nums.size(); i++) {
if (i >= 1 && nums[i] == nums[i - 1] && used[i - 1] == false) {
continue;
}
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, i + 1, used);
path.pop_back();
used[i] = false;
}
}
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
vector<bool> used(nums.size(), false);
backtracking(nums, 0, used);
return result;
}
};
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端