93.复原IP地址
题目链接:93.复原IP地址
难度指数:😀😐😕😫
本期本来是很有难度的,不过 大家做完 分割回文串 之后,本题就容易很多了。
本题不仅要对输入的字符串进行切割,还要对切割的段进行一个合法性的判断。
即使画出了树形结构,在写代码的时候还会有疑惑:切割线如何模拟?切割出来的子串如何表达?
代码思路:
一维数组result,存放分割出来的合法字符串
startIndex,在进入下一层递归的时候,让我们知道是从剩下的字符串里面进行切割。(就是从哪里进行切割)
IP里面 . 的数量决定了树的深度,有3个 . 了就不需要再往下切割(多加 . 就不是合法IP了)。
每次加了 *. *都是对前面字符串进行了合法性的判断;我们还需要判断最后一段是否合法,只有合法了才能加入结果集了。
在代码中,startIndex表示分割线
vector<string> result;
void backtracking(const stirng& s, int startIndex, int pointSum) {
//终止条件
if (pointSum == 3) {
if (isValid(s, startIndex, s.size() - 1)) { //isValid()判断子串是否合法 (数字不能有前导0,区间不能超过255,子串不能有非法字符)
result.push_back(s); //在切割的地方会加上 . 然后直接把s放到结果集
return;
}
}
//单层搜索的逻辑
for (int i = startIndex; i < s.size(); i++) {
//切割之后,对子串进行合法性的判断
if (isValid(s, startIndex, i)) { // [startIndex, i]
s.insert(s.begin() + i + 1, '.');
pointSum += 1;
backtracking(s, i + 2, pointSum); //递归
s.erase(s.begin() + i + 1);
pointSum -= 1;
}
}
}
本题关键在于:如何模拟切割的过程。 其实和我们去做组合问题,去取数的时候是一样的,
另一个难点:要对子串[startIndex, i]进行合法性的判断,
AC代码: (核心代码模式)
class Solution {
private:
vector<string> result; //存放分割出来的合法字符串
void backtracking(string& s, int startIndex, int pointSum) {
//终止条件
if (pointSum == 3) {
if (isValid(s, startIndex, s.size() - 1)) { //isValid()判断子串是否合法
result.push_back(s); //在切割的地方会加上 . 然后直接把s放进结果集
}
return;
}
//单层搜索的逻辑
for (int i = startIndex; i < s.size(); i++) {
//切割之后,对子串进行合法性的判断
if (isValid(s, startIndex, i)) { // [startIndex, i]
s.insert(s.begin() + i + 1, '.');
pointSum += 1;
backtracking(s, i + 2, pointSum); //递归
pointSum -= 1; //回溯
s.erase(s.begin() + i + 1); //回溯删掉逗点
}
else {
break; //不合法,直接结束本层循环
}
}
}
//判断字符串s在左闭右闭区间 [start, end] 所组成的数字是否合法
bool isValid(const string& s, int start, int end) {
if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { //0开头的数字(前导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;
}
public:
vector<string> restoreIpAddresses(string s) {
result.clear();
if (s.size() < 4 || s.size() > 12) {
return result; //算是剪枝了
}
backtracking(s, 0, 0);
return result;
}
};
题目还是比较难的!
78.子集
题目链接:78.子集
难度指数:😀😕🙁
子集问题,就是收集树形结构中,每一个节点的结果。 整体代码其实和 回溯模板都是差不多的。
树形结构:
我们想收获的结果都在(除根节点的)每一个节点里面,都要放进结果集里面。
前面的题目都是在终止条件收获结果,
子集的题目就不是在终止条件收获结果了,而是每进入一层递归,都要把本层递归的单个结果放进结果集。
代码思路:
vector<int> path;
vector<vector<int>> result;
void backtracking(nums, startIndex) {
//收获结果
result.push_back(path); //将单个结果存放在结果集
//终止条件
if (startIndex >= nums.size()) { //说明到叶子节点了
return;
}
for (int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(nums, i + 1); //递归
path.pop_back(); //回溯
}
return;
}
思考:为什么收获结果在终止条件的上面? (为什么不写在终止条件下面?)
答:获取最后一个元素的时候,这个结果才不会被省略掉。
13:00
本题终止条件可以不写(初学者还是建议写,养成习惯),因为当startIndex >= nums.size(),这个for循环也不会执行,
下面照常会终止,本层递归也会结束。
AC代码: (核心代码模式)
class Solution {
private:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, int startIndex) {
//收获结果
result.push_back(path); //将单个结果存放在结果集
//终止条件
if (startIndex >= nums.size()) { //说明遇到叶子节点了
return;
}
//单层搜索的逻辑
for (int i = startIndex; i < nums.size(); i++) {
path.push_back(nums[i]);
backtracking(nums, i + 1); //递归
path.pop_back(); //回溯
}
return;
}
public:
vector<vector<int>> subsets(vector<int>& nums) {
result.clear();
path.clear();
backtracking(nums, 0);
return result;
}
};
90.子集Ⅱ
题目链接:90.子集Ⅱ
难度指数:😀😕🙁
大家之前做了 40.组合总和II 和 78.子集 ,本题就是这两道题目的结合,建议自己独立做一做,本题涉及的知识,之前都讲过,没有新内容。
题目给我们的集合允许有重复元素,
树层上,相邻两个元素,如果数值一样的话,它们所得到的子集就是重复的子集,需要去重。
每一个节点都是我们要收集的结果,(意味着子集问题在收获结果集的时候,代码位置跟组合的题目不一样)。
本题就是在子集的基础上多了2行去重的代码。
代码思路:
需要先将集合进行排序
vector<int> path;
vector<vector<int>> result;
void backtracking(nums, startIndex, used) {
result.push_back(path); //进入递归之后,就将单个节点的结果放到结果集
for (int i = startIndex; i < nums.size(); i++) {
//树层剪枝的过程放在for循环里的第一步
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) { //⚠️需要对树层和树枝做区别
continue;
}
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, i + 1, used); //递归
path.pop_back(); //回溯
used[i] = false;
}
}
需要对树层和树枝进行区分,不然会误杀!
在做回溯算法相关题目的时候,集合中出现了重复元素,如何去重?
基本上都是写上面的去重逻辑
AC代码: (核心代码模式)
class Solution {
private:
vector<int> path;
vector<vector<int>> result;
void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {
result.push_back(path); //进入递归之后,就将单个节点的结果放进结果集
//终止条件
if (startIndex >= nums.size()) {
return;
}
//单层搜索的逻辑
for (int i = startIndex; i < nums.size(); i++) {
//树层剪枝的过程放在for循环里面的第一步
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) {
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) {
result.clear();
path.clear();
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end()); //去重之前先排序
backtracking(nums, 0, used);
return result;
}
};