什么是回溯算法?
回溯算法也是深度搜索算法(DFS),也是递归。
回溯算法是最基本的暴力解决算法,可以很好的解决大多数问题,由此我们需要掌握它。
递归有两点要素:
1. 递归边界。
2. 递归的逻辑——递归"公式"。
递归边界即是需要我们自己寻找的程序出口,这是递归的终点,它将返回子问题的解。
递归公式则理解为对于问题的合理分解。即将一个大问题,合理的将之简化为子问题。也就是说将一个复杂的系统通过合理的方式拆解为单位动作。我们需要研究的也就是如何合理分解。
下面将给出一个递归函数框架(并不是固定不变的,会根据问题的变化而改动),可以进行适当的理解。
返回类型 dfs(形参1,形参2,形参3,...,形参n){
if (递归边界){
return 子问题的解
}
通过合理的方式拆解问题
}
下面举几个例子。
71.子集
问题:
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例:
输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
思路:
这道题要我们返回数组所有的子集(不包含重复子集)。我们将之拆分:
1. 将输出分为三部分,分别输入1位,2位,3位,...,n位。
2. 通过循环,将数组中的后面的数字逐个推进栈中。
3. 建立递归边界,即为判断容器 vector 中的数字达到 k 值没有。
程序:
class Solution {
public:
vector<vector<int>> res;
vector<int> tmpv;
vector<vector<int>> subsets(vector<int>& nums) {
for (int k=0;k<=nums.size();k++) // 将输出分为n部分
dfs(nums,0,k);
return res;
}
void dfs(vector<int> num, int i,int k){
if (i == k) // 判断是否与输出数字量相同
res.push_back(tmpv);
for (int j=i;j<k;j++){ // 循环
tmpv.push_back(num[j]); // 推入栈中
dfs(num,j+1,k); // 递归,加入下一个数
tmpv.pop_back(); // 返回原来的状态
}
}
};
46. 全排列
题目:
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]
思路:
题目让我们返回给定序列所有的排列组合。
由此我们开始拆解问题,将之分成多个子问题:即两个交换元素(当只有两个元素时,交换一次即可。)。
从第一个元素开始作为交换点,不断与后续元素交换,得到新的序列。返回状态,再从第二个元素开始。。。
程序:
class Solution {
public:
vector<vector<int>> res;
vector<int> tmpv;
vector<vector<int>> permute(vector<int>& nums) {
dfs(nums,0);
return res;
}
void dfs(vector<int> &nums,int i){
if (i == nums.size()) //
res.push_back(nums);
for (int j=i;j<nums.size();j++){
swap(nums[j],nums[i]);
dfs(nums,i+1);
swap(nums[j],nums[i]);
}
}
};