81. 允许重复选择元素的组合
81. 允许重复选择元素的组合
题目
给定一个无重复元素 的正整数数组 candidates
和一个正整数 target
,找出 candidates
中所有可以使数字和为目标数 target
的唯一组合。
candidates
中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的唯一组合数少于 150
个。
示例 1:
输入: candidates = [2,3,6,7], target = 7
输出:[[7],[2,2,3]]
示例 2:
输入: candidates = [2,3,5], target = 8
输出:[[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出:[]
示例 4:
输入: candidates = [1], target = 1
输出:[[1]]
示例 5:
输入: candidates = [1], target = 2
输出:[[1,1]]
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate
中的每个元素都是独一无二的。1 <= target <= 500
注意
本题与 LeetCode 第 39 题 相同。
解题思路
这个问题使用回溯法来解决。回溯法是一种通过不断尝试生成问题的所有解的算法,如果解不符合要求,就撤销上一步甚至上几步的计算,再通过其他可能的分支寻找正确的解。具体步骤如下:
定义回溯函数: 定义一个回溯函数
backtrack
,接收一个起始索引start
作为参数,表示从候选数组的哪个位置开始考虑,避免重复组合。回溯过程: 在回溯的过程中,不断选择当前位置的数字,并更新当前组合的和
sum
。如果sum
等于目标值target
,则将当前组合添加到结果集中。递归调用: 如果
sum
小于target
,则继续在当前位置和之后的位置进行递归。如果sum
大于target
,说明当前组合不满足条件,需要撤销当前选择,返回上一层继续尝试其他分支。限制条件: 在递归的过程中,通过判断
sum
是否等于target
来确保生成的组合是符合要求的。递归时传入i
作为参数,表示从当前位置开始尝试,以确保可以重复使用当前数字。返回结果: 最终通过调用
backtrack
函数得到所有符合条件的组合,返回这些组合的数组作为结果。
复杂度分析
- 时间复杂度:
O(n^t)
,n
为candidates
长度,t = target / 最小候选数
,主要由递归树的深度和每层递归中循环次数决定。- 递归树的每一层代表从数组
candidates
中选择一个数字加入到组合中的过程。每个数字都可以重复使用,因此递归树的深度最大为target / 最小候选数
。 - 在最坏情况下,递归树的每个节点都要进行
n
次循环操作来选择候选数,因此递归树的节点总数最多为n^(target / 最小候选数)
。
- 递归树的每一层代表从数组
- 空间复杂度:
O(t)
,t = target / 最小候选数
,主要由递归深度决定,递归树的深度可能达到target / 最小候选数
。
代码
/**
* @param {number[]} candidates
* @param {number} target
* @return {number[][]}
*/
var combinationSum = function (candidates, target) {
const len = candidates.length;
let res = [];
let track = [];
let sum = 0;
const backtrack = (start) => {
// 满足条件
if (sum == target) {
res.push([...track]);
return;
}
// 剪枝
if (sum > target) {
return;
}
for (let i = start; i < len; i++) {
track.push(candidates[i]);
sum += candidates[i];
// 注意这里传入的参数是 i 而不是 i + 1,表示可以重复使用当前的数字
backtrack(i);
track.pop();
sum -= candidates[i];
}
};
backtrack(0);
return res;
};