经典回溯题讲解

目录

      • 题目
      • 解题思路
      • 代码(c++)
      • 总结

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

题目

题目链接:

力扣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;}
};

总结

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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部