题目链接:93. 复原 IP 地址
思路
切割字符串
以每次的startindex为左边界,以for循环的i为右边界
每次切割字符串判断合不合法,合法在i的后面一位加入点,当加了三个点后说明已经分成了四段,就可以停止了,但是还有最后一段没有比较,在加入结果集的时候比较,成功就加入结果集 ,再到回溯函数,再还原进行树层遍历。不符合就直接break,因为这一层不符合后面树层遍历就一定不符合
回溯法
class Solution {
public:
vector<string>result;
void backtracking(string& s, int startindex, int pointSum) {
if (pointSum == 3) {//说明切割成了四段
if (isvaild(s, startindex, s.size() - 1)) result.push_back(s);//比较最后一段
return;//返回下一行的回溯
}
for (int i = startindex; i < s.size(); i++) {
if (isvaild(s, startindex, i)) {
pointSum++;//加了点就加1
s.insert(s.begin() + i + 1, '.');//再后面加点
backtracking(s, i + 2, pointSum);//因为要跳过点所以再下下层再开始加点
s.erase(s.begin() + i + 1);//回溯,到上一层
pointSum--;//为了返回上一层还没有变过
} else {
break;//直接break,后面循环一定不符合
}
}
}
bool isvaild(string& s,int begin, int end) {,
if (begin > end) return false;//当pointSum后i+2可能已经出去了,说明没有第四段,就不符合
if (begin != end && s[begin] == '0') return false;//开头为0且不只有一个元素再这一层切割中
int sum = 0;
for (int i = begin; i <= end; i++) {
if (s[i] - '0' > 9 || s[i] - '0' < 0) {//判断是不是数字
return false;
}
sum = sum * 10 + s[i] - '0';
if (sum > 255) return false;//看下ip符不符合
}
return true;
}
vector<string> restoreIpAddresses(string s) {
backtracking(s, 0, 0);
return result;
}
};
递归三部曲
确定递归函数参数
除了需要题目给的参数外,还需要startindex来判断树枝遍历的起始位置,避免重复
如果没有pointSum,只有startindex,不能判断什么时候分成了四段结束了,所以需要
确定终止条件
当分成了四段后我们就可以提前结束了,但是最后一段还没有比较,所以如果第四段还对u,就可以加入结果集了
返回到回溯函数进行数层遍历
确定单层搜索的逻辑
先判断这一个分割块符不符合,不符合后面一定不符合,所以就可以break了
每次遍历判断startindex和i这个阶段的分割块是否符合条件,符合条件就加入,点加1,再树枝遍历下一个分割块(递归函数)
遍历完后到达不能遍历情况下返回上一层遍历不同的分割块,树层遍历。
isvaild函数
比较重要的一点就是begin > end
当遍历到第三段,又需要i+2=startindex进入第三层比较第四段,i+2可能超出了s的最大范围,没有第四段,所以就返回false
总结
和上一题不同的是这里可以控制分割成几段,用一个pointSum来配合。
题目链接:78. 子集
回溯法
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
void backtracking(vector<int>& nums, int startindex) {
result.push_back(path);
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) {
backtracking(nums, 0);
return result;
}
};
集合问题和组合问题的差别
集合问题加入集合的时候直接加入
组合问题加入结果集中有限制
题目链接:0. 子集 II
思考
这题和组合问题II一样,都是有重复元素在元素组,但是不能重复,所以我们必须要考去重
经过推理,树枝遍历相同元素是可以加入的,树层遍历相同元素就要去除
怎么知道是什么遍历>
选择一个used数组,当树枝遍历是下一层还上一个已经遍历过了,说明是是树枝遍历可以加入
当used数组为false,说明used已经还原,进入到for循环下一层,是树层遍历
2.递归法
使用used数组
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
void backtracking(vector<int>& nums, int startindex, vector<bool>& used) {
result.push_back(path);
for (int i = startindex; i < nums.size(); i++) {
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
path.push_back(nums[i]);
used[i] = true;
backtracking(nums, i + 1, used);
used[i] = false;
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool>used(nums.size(), false);
sort(nums.begin(), nums.end());
backtracking(nums, 0, used);
return result;
}
};
不使用startindexz
class Solution {
public:
vector<vector<int>>result;
vector<int>path;
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();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(), nums.end());
backtracking(nums, 0);
return result;
}
};
为什么这种方法也行?
因为这种方法也可以判断是不是树枝遍历,当为树枝遍历时每次都是startindex==i,都是处于第一层
但是当for循环遍历了,i就不等于startindex.说明是树层遍历了
回溯后,一开始是树层遍历,但是startindex又按照这个i,所以下一层又是树枝遍历
递归三部曲
确定递归函数的参数
除了题目给的参数外,因为不能够元素重复用startindex.判断遍历方式用used数组
确定终止条件
因为可以到这里,集合没限制条件,就可以加入了
确定单层循环的逻辑
每次加入元素就判断,符不符合条件再加入元素。进行树枝遍历,回溯再进行树层遍历