算法-回溯算法总结
回溯与递归的区别
回溯的本质是穷举,回溯一定代表有递归
递归就一直往深处递归就好了,但是回溯还伴随着递归结束之后的”回溯操作“,例如递归中处理的+1,在回溯中要-1。
回溯的算法思路
一般都是返回void,参数不能一下子全部想定,都是边写,边设定要传入的参数
void transback(参数) {
if (条件) {
保存结果;
return;
}
for (当下的宽度遍历) {
递归处理;
递归
回溯
}
}
遍历的范围就是宽度,迭代的次数就是深度,如此遍历下来。
剪枝操作
有些遍历明显不符合要求了,就可以不遍历,于是剪枝操作让这部分不遍历。
例题
找出所有相加之和为 n
的 k
个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
- 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回
思路
其实就相当于在1-9里面,找k个不重复的数的组合,并且这些组合里的数相加等于n。
本质上就是寻找需要的组合,无序,只不过在回溯的时候,可能要多处理一下关于和的问题
- 从宽度遍历,1-9的遍历,
- 处理操作,例如将这个数加入容器中,接着深度遍历,就看 这个数+1 至9之间再次遍历然后递归,直到找到了k个数,且他们的和为题目要求的,返回一个结果 (递归操作)
- 那么遍历递归完“内部”的数之后,需要“外部”的下一个数及其子路径的遍历,因此要将这一个数从容器中去掉,等待下一次遍历。(PS:这里的内部外部是相对于本层循环来说的,这一行就是回溯操作)
- 然后就是退出条件,当找到了k个之后,无论怎么样都会退出,但如果你的和是等于n,那么你的结果留下来之后,你再退出。
思路完了之后,就先确认退出条件,不一定准确,但构思一下,然后就写其中的逻辑
代码
class Solution {
public:
//宽度是从1-9
void findsum (int maxsize, int targetsum, int basestart, int sum) {
if (sum > targetsum) return;
else if (temp.size() == maxsize && sum == targetsum) {
result.push_back(temp);
return;
}
if (temp.size() == maxsize) return;
for (int i = basestart; i <= end; i++) {
temp.push_back(i);
sum += i;
if (sum > targetsum) { // 提前剪枝
temp.pop_back();
sum -= i;
return;
}
findsum(maxsize, targetsum, i + 1, sum);
temp.pop_back();
sum -= i;
}
}
vector<vector<int>> combinationSum3(int k, int n) {
findsum (k, n, start, 0);
return result;
}
private:
vector<int> temp; //定义了一个临时数组
vector<vector<int>> result; //定义了一个结果数组
int start = 1; //遍历头,因为题目中说1-9
int end = 9;
};
问题
我自己做的时候就是剪枝这个地方出了问题
你要么不剪,要么剪完
剪了也记得要把回溯要做的东西给做了,不然会出现问题,例如这一次加的数他虽然没用了,但不做回溯处理,他就一直加在那里,会对后面结果有影响
if (sum > targetsum) { // 提前剪枝
temp.pop_back();
sum -= i;
return;
}
另一道相似题目
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target 的不同组合数少于 150 个。
思路
这一题与上一题不同在于可以重复,那么为了保证有重复的数,但组合不同,可以这样:
每一次从几开头就从几开始遍历,避免了1->1->1->2和1->2->1->1这样重复的情况
这里有两种写法:
第一种写法,是我自己的,这个重点是要先进行排序,因为是在回溯的for里先判断是否sum > target的,然后就返回了,这样做的坏处就是,如果candidates是无序的,后面的数比前面的数要小,就会导致后面的数也不会遍历了,直接就整个setsum函数就return了。
所以必须先排序。
class Solution {
public:
void setsum (vector<int>& candidates, int startIndex, int target, int sum) {
if (sum == target) {
result.push_back(temp);
return;
}
for (int i = startIndex; i < candidates.size(); i++) {
temp.push_back(candidates[i]);
sum += candidates[i];
// 不同之处/
if (sum >target) {
temp.pop_back();
sum -= candidates[i];
return;
}
setsum (candidates, i, target, sum);
temp.pop_back();
sum -= candidates[i];
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
setsum(candidates, startIndex, target, initSum);
return result;
}
private:
vector<int> temp;
vector<vector<int>> result;
int initSum = 0;
int startIndex = 0;
};
第二种写法:
这里就直接在开头判断,还是能正常进行其他递归遍历的。
class Solution {
public:
void setsum (vector<int>& candidates, int startIndex, int target, int sum) {
if (sum == target) {
result.push_back(temp);
return;
}
//不同之处
if(sum > target) return;
for (int i = startIndex; i < candidates.size(); i++) {
temp.push_back(candidates[i]);
sum += candidates[i];
setsum (candidates, i, target, sum);
temp.pop_back();
sum -= candidates[i];
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
setsum(candidates, startIndex, target, initSum);
return result;
}
private:
vector<int> temp;
vector<vector<int>> result;
int initSum = 0;
int startIndex = 0;
};