回溯算法团灭leetcode组合数

回溯算法团灭leetcode组合数

算法介绍:
首先,我们来介绍以下什么是回溯算法?
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
简单来说,回溯算法就是遍历所有的可能,直到找到满足要求的那种情况。事实上,回溯算法和深度优先搜索并没有什么太大的区别!
废话不多说,直接来算法框架:

res = [];
void backtrace(路径, 选择列表){
	if (path满足要求){
		res.push_back(path);
		return;
	}
	for (auto 选择:选择列表){
		做选择;
		backtrace(路径, 选择列表);
		撤销选择;
	}
}
以上框架就是回溯算法的伪代码框架,我们的代码核心就是for循环里面的每一次选择,话不多说,我们从题目中学习。

39.组合总和
在这里插入图片描述
首先,我们每拿到一道回溯算法的题目,我们就需要将这个决策树给画出来,从题目的意思我们可以画出这么一棵树:
在这里插入图片描述
每一个箭头就是一次选择,圆圈里的数就是当前数的和,如果它满足target,那么就把这条路劲加入到res中。
我们可以先考虑没有剪枝的情况,按照上面给出的回溯算法框架,我们应该这么写:

vector<vector<int> > res;
vector<int> path;
void dfs(vector<int>& nums, int target, int sum){
	if(sum > target) return;
 	if(sum == target){
		res.push_back(path);
		return ;
	}
	for(int i=0;i<nums.size();i++){
		sum += nums[i];
		path.push_back(nums[i]);
		dfs(nums,target,sum);
		path.pop_back();
		sum -= nums[i];
	}
}

按照没有剪枝的算法来写,跑出来的结果如下图所示:
在这里插入图片描述
我们可以看到,答案要求我们不考虑顺序,那么我们应该如何考虑把重复的枝叶减去呢?
遇到这一类相同元素不计算顺序的问题,我们在搜索的时候就需要按某种顺序搜索。具体的做法就是:每一次搜索的时候设置下一轮搜索的起点start,请看下图:
图片来自与leetcode题解
也就是说,我们在遍历第二个数的时候就不再考虑第一个数,因此我们可以考虑使用一个start变量来记录当前选择的位置,具体代码如下:

class Solution {
private:
    vector<int> path;
    vector<vector<int>> res;
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        dfs(candidates, target, 0, 0);
        return res;
    }

    void dfs(vector<int>& candidates, int target, int sum, int start){
        if(sum > target) return;
        if(sum == target){
            res.push_back(path);
            return ; 
        }
        for(int i=start;i<candidates.size();i++){
            sum += candidates[i];
            path.push_back(candidates[i]);
            dfs(candidates, target, sum, i);
            path.pop_back();
            sum -= candidates[i];
        }
    }

};

40.组合总和Ⅱ
在这里插入图片描述
在看到这一题,这一题和上一题的不同在于,candidates数组里的数字,只能使用一次,那我是不是可以每次选择的时候,不从start开始,而从start+1开始呢?
我们先按照这种思路跑一个结果看看:

vector<vector<int> > res;
vector<int> path;
void dfs(vector<int>& nums, int target, int sum, int start){
	if(sum > target) return;
 	if(sum == target){
		res.push_back(path);
		return ;
	}
	for(int i=start;i<nums.size();i++){
		sum += nums[i];
		path.push_back(nums[i]);
		dfs(nums,target,sum, i+1);
		path.pop_back();
		sum -= nums[i];
	}
}

按照上面的代码,跑出来的结果是这样的:
在这里插入图片描述
我们可以看到[1,2,5]和[2,1,5]重复了,[1,7]和[7,1]重复了,那么我们应该怎么减枝呢?
这时候,我们可以考虑先对数组进行升序排序,重复的元素一定不是排好序以后相同的连续数组区域的第i个元素。也就是说,剪枝发生在:同一层数值相同的结点第2,3…个结点,因为数值相同的第1个结点已经搜索除了包含了这个数值的全部结果,同一层的其它结点,候选数的个数更少,搜索出的结果一定不会比第1个结点更多,并且是第1个结点的子集。
具体实现如下:

class Solution {
private:
    vector<vector<int>> res;
    vector<int> path;
public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        dfs(candidates, target, 0, 0);
        return res;
    }

    void dfs(vector<int>& candidates, int target, int sum, int start){
        if(sum > target) return;
        if(sum == target){
            res.push_back(path);
            return;
        }
        for(int i=start;i<candidates.size(); i++){
            if(i>start && candidates[i] == candidates[i-1]) continue;
            sum += candidates[i];
            path.push_back(candidates[i]);
            dfs(candidates, target, sum, i+1);
            path.pop_back();
            sum -= candidates[i];
        }
    }

};

216.组合总和Ⅲ
在这里插入图片描述
对于这一道题,和之前的不一样的点就是在于,不能有重复的数字,那不就是在下一个数字的位置进行选择吗?
设置一个i=start变量记录当前选择的位置,然后依次i+1来选择,具体代码如下:

class Solution {
private:
    vector<vector<int>> res;
    vector<int> path;
    int hash[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        dfs(k, n, 0, 0, 0);
        return res;
    }

    void dfs(int k, int n, int depth, int sum, int start){
        if(depth == k && sum == n){
            res.push_back(path);
            return ;
        }
        for(int i=start;i<9 && sum < n;i++){
            sum += hash[i];
            path.push_back(hash[i]);
            dfs(k, n, depth+1, sum, i+1);
            path.pop_back();
            sum -= hash[i];
        }
    }

};

我们用这三道题来讲解回溯算法,希望大家能够对这个算法能够有所了解,如果能够理解这三道题,接下来两道二叉树的题,不也是一样的套路吗?

113.路径总和Ⅱ
事实上,回溯算法,或者说深度优先搜索算法,在二叉树中的使用,其实就是树的前、中、后序遍历,如果你能够真正理解树的前中后序遍历,再结合本文,一定可以解答这道题:
在这里插入图片描述
我们每一次的选择,不就是选择左子树或者右子树吗?
具体代码如下:

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
private:
    vector<vector<int>> res;
    vector<int> path;
public:
    vector<vector<int>> pathSum(TreeNode* root, int sum) {
        if(root == NULL) return res;
        dfs(root, sum);
        return res;
    }

    void dfs(TreeNode* root, int sum){
        if(root == NULL) return;
        sum -= root->val;
        path.push_back(root->val);
        if(!root->left && !root->right && !sum){
            res.push_back(path);
        }
        dfs(root->left, sum);
        dfs(root->right, sum);
        path.pop_back();
        sum+=root->val;
    }

};

最后还是希望多多实践,多多思考,多多总结,才能真正理解这个算法!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值