经典回溯题讲解

本篇博客主要讲解一下回溯算法的典型例题。为什么我没有选力扣里的组合总和Ⅰ、Ⅲ或Ⅳ呢?因为除了Ⅳ其他三题都是回溯算法,三题相差不大,Ⅳ是一道动态规划的题我们后面再说。

题目

题目链接:

力扣40:组合总和Ⅱ

给定一个数组 candidates和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

注意:解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

示例 2:

输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]

提示:

  • 1 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

解题思路

先看题目,有几个要求:

  1. 找到和为target的数字组合
  2. 数组candidates中的每个数字只能使用一次(每一个组合中只能用一次)
  3. 解集不能包含重复组合

然后根据要求可以想到需要有一个标记的数组(我用f表示),其长度应该和数组candidates的长度一致。需要返回一个二维的vector容器(num),关于解集去重我们先不想,先想如何找到和为target的数组组合。

这类题型应该可以很容易看出可以用回溯求解,因为数组中的每个元素都要遍历取值相加,发现不符合题意就回溯和还原。那么知道用回溯求解就好办了:

首先要想的是递归的函数要有哪些参数?需要遍历candidates数组,肯定要有该数组。既然是要求和,一定要一个参数放置累加的和(m)。

然后要想的是什么条件下返回(回溯),由题目可知当m等于target时,满足题意,将所存储的组合数放入需要返回的数组num中。当m大于等于target时,如果继续递归下去或者说继续累加下去永远不可能得到目标数,所以此时应该回溯。

由于返回的是二维的,所以可以用一个一维(ber)先存储每次遍历的组合,当符合条件时再将其放入二维里

再然后应该想的是递归函数里需要做什么事情,这里可以用一个例子或一个数据带入想,因为在每次递归里所做的事都是一样的,对一个例子成立对其他例子也一样成立。所以就拿示例一讲吧!当第一次进入递归时,我们要对这个candidates遍历,因为每个元素都需要和其他元素进行相加才能找到每个正确答案,这里就要一个for循环。每次遍历都需要将遍历的数加起来,同时也要将遍历的数放入一维容器里。这时这一层递归就基本结束了,因为要做的事已经做了,可以进入下一层递归了。一层递归结束后,我们需要把之前更改的变量值都还原。就像每次吃完饭都要把碗洗干净,这样下次吃饭的时候可以和这次吃饭的时候使用同一个碗。伪代码如下:

for (int i = 0; i < candidates.size(); ++ i) {
	if (这个数没有使用过) {
		累加i;
		将i放入容器中;
		还要将判断这个数没有使用过的标记数组置为true;
		//此时要做的事做完了
		dfs(candidates, target, 累加的数);
		//还原
		将累加的数减去i;
		将容器的i弹出;
		将标记数组置false;
	}
}

至此,主体已经完成的差不多了,可以去解决组合总和,那题和这题的区别主要在这题多了去重,我们看看去重的部分:

开始我没想到好的方法,我想遍历一遍返回数组num检查要放入的数组ber是否在num中已经存在,后面我放弃了。。。

然后我打开评论区,妙啊!这里我来讲解一下。我们可以先用sort函数将需要遍历的数组candidates排序,然后相同项就会放在一起。我们以实例一为例:当遍历第一个相同项1时,我们可以使用后面的相同项。但是当遍历到第二个相同项1时,如果不加任何措施会出现两个[1,7]和[1,2,5]数组,这时可以看到凡是1单独出现的数组都会重复,所以加一个判断条件,当candidates[i] == candidates[i - 1]时,直接跳过。但还有个细节的是,数组[1,1,6]没法输出了,那就再加个条件:!f[i - 1]。如果还不清楚可以看代码理解。

代码(c++)

class Solution {
public:
    vector<vector<int>> num;
    vector<int> ber;
    bool f[105];
    void dfs(vector<int> candidates, int target, int n, int m) {
        if (m > target)
            return ;
        else if (m == target) {
            num.push_back(ber);
            return ;
        }
        for (int i = n; i < candidates.size(); ++ i) {
            if (i > 0 && candidates[i] == candidates[i - 1] && !f[i - 1]) //去重
                continue;
            if (!f[i]) {
                ber.push_back(candidates[i]);
                f[i] = true;
                dfs(candidates, target, i + 1, m + candidates[i]);
                ber.pop_back();
                f[i] = false;
            }
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        if (candidates.size() == 0)
            return num;
        sort(candidates.begin(), candidates.end());
        dfs(candidates, target, 0, 0);
        return num;
    }
};

总结

对于组合总和Ⅲ,我觉得可以举一反三,这题懂了那题也很容易。递归回溯这类题,其实多写一点就熟练了,你会感觉就像模板一样,拿到题直接套就行。

评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

聆听逝去的流

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值