第七章 回溯算法 part03
93. 复原IP地址
本期本来是很有难度的,不过大家做完 分割回文串 之后,本题就容易很多了。
题目链接/文章讲解:https://programmercarl.com/0093.%E5%A4%8D%E5%8E%9FIP%E5%9C%B0%E5%9D%80.html
视频讲解:https://www.bilibili.com/video/BV1XP4y1U73i/
有效的 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 地址。
这是判断条件,判断子串是否合法
段位以0为开头的数字不合法
段位里有非正整数字符不合法
段位如果大于255了不合法
又学到了一点
但是,实际在测试的时候,时间超出限制,正则表达式的复杂度较高,如果要提高效率,可以使用简单的范围检查代替正则表达式。
bool isValidNumber(const std::string& str) {
// 正则表达式,匹配 0 到 255 且没有 0 开头的数字(除非是单独的 0)
std::regex pattern("^(0|[1-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
// 使用std::regex_match来匹配字符串
return std::regex_match(str, pattern);
}
^(0|[1-9][0-9]?|1[0-9]{2}|2[0-4][0-9]|25[0-5])$
正则表达式解释:
^ 和 $:分别匹配字符串的开头和结尾,确保整个字符串完全匹配。
0:匹配单独的数字0。
[1-9][0-9]?:匹配1到99之间的数字。[1-9]确保数字不以0开头,而[0-9]?表示后面可以跟一个数字(0到9)。
1[0-9]{2}:匹配100到199之间的数字。1开头,后面跟两个数字([0-9]{2})。
2[0-4][0-9]:匹配200到249之间的数字。2开头,第二个数字是0到4,第三个数字是0到9。
25[0-5]:匹配250到255之间的数字。
不得不说C++的库确实好强大
class Solution {
public:
vector<string> result;
bool isValid(string s, int startIndex,int i)
{
string str = s.substr(startIndex, i - startIndex + 1);
if (str.empty() || str.size() > 3) return false;
if (str[0] == '0' && str.size() > 1) return false;
int num = stoi(str);
return num >= 0 && num <= 255;
}
void backtracking(string& s, int startIndex, int pointNum) {
if (pointNum == 3) {
if (isValid(s, startIndex, s.size() - 1)) {
result.push_back(s);
}
return;
}
for (int i = startIndex; i < s.size(); i++)
if (isValid(s, startIndex, i)) {
s.insert(s.begin() + i + 1 , '.');
pointNum++;
backtracking(s, i + 2, pointNum);
pointNum--;
s.erase(s.begin() + i + 1);
}
else break;
}
vector<string> restoreIpAddresses(string s) {
result.clear();
if (s.size() < 4 || s.size() > 12) return result;
backtracking(s, 0, 0);
return result;
}
};
这个算法的核心是通过递归和回溯,在每个合法的位置插入点号,从而生成所有可能的 IP 地址组合。
isValid 函数负责判断当前部分的子串是否合法,而 backtracking 函数则通过插入点号、递归处理和回溯来逐步构造所有的可能解。
我发现回溯算法的代码都挺简单的,思路挺简单,但是写并不好写,一看就会,一做就废,还得多学多练。
78. 子集
子集问题,就是收集树形结构中,每一个节点的结果。整体代码其实和回溯模板都是差不多的。
题目链接/文章讲解:https://programmercarl.com/0078.%E5%AD%90%E9%9B%86.html
视频讲解:https://www.bilibili.com/video/BV1U84y1q7Ci
这题和之前的子集题目几乎一模一样,我都思考了半天去重的问题,结果发现没有重复的数据,那不是易如反掌。有手就行。简单到不能再简单了。不解释。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
vector<bool> used;
void backtracking( vector<int>& nums ,int startIndex){
result.push_back(path);
if (path.size() <nums.size()) {
for(int i=startIndex;i<nums.size();i++)
{
path.push_back(nums[i]);
backtracking(nums,i + 1); //继续递归
path.pop_back();//回溯
}
}
}
vector<vector<int>> subsets(vector<int>& nums) {
result.clear(); // 可以不写
path.clear(); // 可以不写
backtracking(nums, 0);
return result;
}
};
90. 子集II
大家之前做了 40.组合总和II 和 78.子集,本题就是这两道题目的结合。建议自己独立做一做,本题涉及的知识,之前都讲过,没有新内容。
题目链接/文章讲解:https://programmercarl.com/0090.%E5%AD%90%E9%9B%86II.html
视频讲解:[https://www.bilibili.com/video/BV1vm4y1F71J]
本题本质上和去重子集完全一样
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
vector<bool> used;
void backtracking( vector<int>& nums ,int startIndex){
result.push_back(path);
for(int i=startIndex;i<nums.size();i++)
{
if (i > startIndex && nums[i] == nums[i - 1]) {
continue;
}
path.push_back(nums[i]);
backtracking(nums,i + 1); //继续递归
path.pop_back();//回溯
}
//满足条件,return
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
result.clear(); // 可以不写
path.clear(); // 可以不写
sort(nums.begin(), nums.end());
backtracking(nums, 0);
return result;
}
};
(https://www.bilibili.com/video/BV1vm4y1F71J)
本题也可以不使用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0。
如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。
今天的还是比较简单的,跟上次的子集合差不多。