39. 组合总和
「题目:」
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates 中的同一个数字可以无限制重复被选取。如果至少一个数字的被选数量不同,则两种组合是不同的。
「示例:」
输入:candidates = [2,3,6,7], target = 7,输出:[[2,2,3],[7]]。
「解题思路:」
寻找可行解的题型,可以使用搜索回溯法,其本质上就是暴力枚举,时间复杂度会非常高,通常需要针对其回溯的条件进行剪枝优化。
枚举的过程中需要记录当前剩余的 target 值,当前选择的元素以及当前数组的下标。
当超出数组下标或者剩余 target 值为零时,即为递归终止条件。
枚举过程中,可以直接跳过该元素,但是如果当前元素与 target 的差值大于等于零,可以选择该元素。
时间复杂度:O(2^n),空间复杂度:O(n)。
const combinationSum = (candidates, target) => {
const ans = [];
const dfs = (target, combine, index) => {
if (target === 0) {
ans.push(combine);
return;
}
if (index === candidates.length) {
return;
}
// 不选择当前数字
dfs(target, combine, index + 1);
// 选择该数字
if (target - candidates[index] >= 0) {
dfs(target - candidates[index], [...combine, candidates[index]], index);
}
}
dfs(target, [], 0);
return ans;
}
相比较上述回溯的实现方式,下面这种方式更加优雅:
const combinationSum = (candidates, target) => {
const ans = [];
const path = [];
const backtrack = (start, sum) => {
if (sum === target) {
ans.push([...path]);
return;
}
if (sum > target) {
return;
}
for (let i = start; i < candidates.length; i++) {
// 选择
path.push(candidates[i]);
sum += candidates[i];
backtrack(i, sum);
// 不选择。状态回滚
path.pop();
sum -= candidates[i];
}
}
backtrack(0, 0);
return ans;
}
40. 组合总和 II
「题目:」
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
「示例:」
输入: candidates = [10,1,2,7,6,1,5], target = 8,输出:[[1,1,6],[1,2,5],[1,7],[2,6]]。
「解题思路:」
这道题相比较上一题加了一些限制条件:
每个数组只使用一次。
最终能到的组合不重复。
只要在枚举的过程中都移动 index 下标即可保证每个数字只使用一次。
针对不重复组合,第一时间会想到通过哈希表来对结果去重,但是这种方式并不能降低算法本身的时间复杂度,所以需要分析回溯的条件,减少重复回溯的消耗,从而达到剪枝优化的目的。
出现重复组合的根本原因在于:每层枚举的过程中,重复处理了已经处理过的数字。
为了能够方便过滤掉本层重复处理的数字,需要对数组进行排序预处理。
时间复杂度:O(2^n),空间复杂度:O(n)。
const combinationSum2 = function(candidates, target) {
const ans = [];
const path = [];
candidates.sort((a, b) => a - b);
const backtrack = (start, sum) => {
if (sum === target) {
ans.push([...path]);
return;
}
if (sum > target) {
return;
}
for (let i = start; i < candidates.length; i++) {
// 剪枝:过滤掉同层相同的元素
if (i - 1 >= start && candidates[i - 1] === candidates[i]) {
continue;
}
// 选择
path.push(candidates[i]);
sum += candidates[i];
backtrack(i + 1, sum);
// 不选择。状态回滚
path.pop();
sum -= candidates[i];
}
}
backtrack(0, 0);
return ans;
};
216. 组合总和 III
「题目:」
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
「示例:」
输入: k = 3, n = 7,输出: [[1,2,4]]。
「解题思路:」
相比较上一题,本道题虽然抽象了目标值和组合元素个数的限制,但是并没有改变此类问题的通用解法,只不过是变更了递归终止的条件。
时间复杂度:O(2^9),空间复杂度:O(n)。
const combinationSum3 = function(k, n) {
const candidates = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const ans = [];
const path = [];
const backtrack = (start, sum) => {
if (path.length === k && sum === n) {
ans.push([...path]);
return;
}
if (path.length > k || sum > n) {
return;
}
for (let i = start; i < candidates.length; i++) {
path.push(candidates[i]);
sum += candidates[i];
backtrack(i + 1, sum);
path.pop();
sum -= candidates[i];
}
}
backtrack(0, 0);
return ans;
};
377. 组合总和 Ⅳ
「题目:」
给你一个由不同整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
「示例:」
输入:nums = [1,2,3], target = 4,输出:7。
「解题思路:」
本道题求解最终组合的排列个数,如果还是采用回溯枚举的方式,时间复杂度会非常的高,所以本题只要求求解可行解的个数,而非具体的排列。
求可行解个数的题型,一般采用动态规划来解决,它相比较回溯,主要快在能够消除重叠子问题。
设 dp[target] 表示形成 target 目标值的排列个数:
边界情况:当 target = 0 时,只有不选择任何元素,才能使得元素之和等于 0,所以 dp[0] = 1。
状态转移方程:遍历 sum 从 1 到 target,再嵌套遍历 nums,如果 num <= sum,那么当前 sum 的排列数就包含 sum - num 的排列数,即 dp[sum] += dp[sum - num]。
在动态规划的过程中,通过 dp[sum] 临时状态的记忆来消除了回溯中 backtrack(sum) 这样的重叠子问题的消耗,这是典型的空间换时间的优化手段。
时间复杂度:O(target*n),空间复杂度:O(target)。
const combinationSum4 = function(nums, target) {
const dp = Array(target + 1).fill(0);
dp[0] = 1;
for (let sum = 1; sum <= target; sum++) {
for (const num of nums) {
if (num <= sum) {
dp[sum] += dp[sum - num];
}
}
}
return dp[target];
}
写在最后
「感谢您能耐心地读到这里,如果本文对您有帮助,欢迎点赞、分享、或者关注下方的公众号哟。」
相关链接:
《39. 组合总和》 https://leetcode-cn.com/problems/combination-sum/
《40. 组合总和 II》 https://leetcode-cn.com/problems/combination-sum-ii/
《216. 组合总和 III》 https://leetcode-cn.com/problems/combination-sum-iii/
《377. 组合总和 Ⅳ》 https://leetcode-cn.com/problems/combination-sum-iv/