一、回溯
解决一个回溯问题,实际上就是一个决策树的遍历过程,只需要考虑三个问题。
1、路径:也就是已经做出的选择。
2、选择列表:就是当前可以做的选择。
3、结束条件:也就是到达决策树底层,无法再做选择的条件。
回溯算法框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径,选择列表)
撤销选择
例题:全排列问题
如上:2是路径,表示已经做出的选择,1,3是选择列表,即可以做出的选择。
代码(java):
List<List<Integer>> res = new LinkedList<>();
//输入一组不重复的数组,返回他们的全排列
List<List<Integer>> permute(int[] nums) {
// 记录「路径」
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}
//路径记录在track中
//选择列表:nums中不存在于track的那些元素
//结束条件:nums中的元素全部在track中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}
for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
例题
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> pathSum(TreeNode* root, int target) {
recur(root,target);
return res;
}
void recur(TreeNode* root,int target)
{
if(root==nullptr) return;
path.push_back(root->val);
target-=root->val;
if(target==0&&root->left==nullptr&&root->right==nullptr)
res.push_back(path);
recur(root->left,target);
recur(root->right,target);
path.pop_back(); 重要!!!!!!向上回溯前,需要将当前节点从路径 path 中删除,即执行 path.pop() 。
}
二、递归
每当递归函数调用自身时,他都会将给定的问题拆解为子问题,递归调用继续进行,直到子问题无需进一步递归就可以解决的地步。
例子:反转字符串
递归应该关注的三个点:
1、终止条件
2、本级递归的任务
3、返回上一级的信息
针对本题来讲
本题的终止条件是 数组下标移动到中间位置以后便不再交换
本级递归层要完成的是交换两个字符
返回值:传递给下一次递归的是 字符串和下标
class Solution {
public:
void reverse(vector<char>& s,int i){
//1.终止条件
int len=s.size();//获取字符的长度
if(i>=(len/2)){
return;
}
//2.交换首尾
char c=s[i];
s[i]=s[len-i-1];
s[len-i-1]=c;
//3.返回给上一层递归的参数
reverse(s,i+1);
}
void reverseString(vector<char>& s) {
reverse(s,0);
}
};
其实写递归不能想着去把递归平铺展开,一层层的下调再返回会搞乱,其实只要找到递推公式就可以轻松的写出递归代码。