以下是今日份的总结
39 组合总和
40 组合总和II
131 分割回文串
如何理解回溯法
回溯法解决的问题都可以抽象为树形结构,指的是所有回溯法的问题都可以抽象为树形结构!
因为回溯法解决的都是在集合中递归查找子集,集合的大小就构成了树的宽度,递归的深度就构成了树的深度。
递归就要有终止条件,所以必然是一棵高度有限的树(N叉树)。
今天的题目难度不低,尽量还是写一些简洁代码 ^ _ ^
组合总和
思路:
数组内数字可以重复,所以回溯的要求是target减去当前数值的差
值得注意的是
减法需要判断元素数值比目标数值大的情况,以及当前回溯法在做减法的时候适用于有序的情况
无重复元素,不考虑数组去重的情况
递归
vector<vector<int>> res;
vector<int> path;
void backTack(vector<int>& candidates, int target, int start) {
// 结束条件
if (target == 0) {
res.push_back(path);
return;
}
// 递归
for (int i = start; i < candidates.size(); i++) {
if (target - candidates[i] < 0)
return;
else {
target -= candidates[i];
path.push_back(candidates[i]);
backTack(candidates, target,i); // 因为数字可以重复所下位可从自生再出发
path.pop_back();
target += candidates[i];
}
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
// 判空
if (candidates.size() == 0)
return vector<vector<int>>();
sort(candidates.begin(),candidates.end());//在有序的前提下
backTack(candidates, target, 0);
return res;
}
组合总和II
思路:
回溯法,和上一题思路一样
值得注意的是
出现重复元素需要考虑去重问题
递归
vector<vector<int>> res;
vector<int> path;
void backTack(vector<int>& candidates, int target, int start) {
// 结束条件
if (target == 0) {
res.push_back(path);
return;
}
// 递归
for (int i = start; i < candidates.size(); i++) {
if(i>start&&candidates[i] == candidates[i-1]){
continue;
}
if (target - candidates[i] < 0) {
return;
}
else {
target -= candidates[i];
path.push_back(candidates[i]);
backTack(candidates, target,i + 1); // 因为数字可以重复所下位可从自生再出发
path.pop_back();
target += candidates[i];
}
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
// 判空
if (candidates.size() == 0)
return vector<vector<int>>();
sort(candidates.begin(), candidates.end()); // 在有序的前提下
backTack(candidates, target, 0);
return res;
}
分割回文串
思路:
切割问题可以抽象为组合问题
如何模拟那些切割线
切割问题中递归如何终止
在递归循环中如何截取子串
如何判断回文
值得注意的是
判断回文串
分割字符串,类似于组合问题,但是递归时增长的是分割的长度
递归
// 判断回文串
bool isPalindrome(const string& s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
if (s[i] != s[j]) {
return false;
}
}
return true;
}
vector<vector<string>> res;
vector<string> path; // 放已经回文的子串
void backtrack(const string& s, int start) {
// 如果起始位置已经大于s的大小,说明已经找到了一组分割方案了
if (start >= s.size()) {
res.push_back(path);
return;
}
for (int i = start; i < s.size(); i++) {
if (isPalindrome(s, start, i)) { // 是回文子串
// 获取[startIndex,i]在s中的子串
string str = s.substr(start, i - start + 1);
path.push_back(str);
} else { // 如果不是则直接跳过
continue;
}
backtrack(s, i + 1); // 寻找i+1为起始位置的子串
path.pop_back(); // 回溯过程,弹出本次已经添加的子串
}
}
vector<vector<string>> partition(string s) {
backtrack(s, 0);
return res;
}
写在最后
----OK,今日份的博客就写到这里,这一期的回溯法很巧秒啊,也涉及到,明天继续加油!!!
—看了看下期的题,但是我的栈还有一节没写;
–追上时间进度了吗?如追,从欠三天变成欠二天!!(笑
-青梅汁快酿好了,一起喝吧。