编码母题 / P1 / 经典算法 / 14
组合总和与回溯剪枝
重要程度高价值难度中等预计完成35–45 分钟
题目是什么
输入可含重复值的正整数数组 candidates 和非负整数 target;每个数组位置最多选一次,返回和为 target 的所有不重复组合。每组内升序,组合列表按字典序排列;target=0 返回 [[]],且不得修改输入。
输入输出示例
candidates=[1,1,2]、target=2 返回 [[1,1],[2]];两个值为 1 的位置可一起选,但相同数值组合只返回一次。
练习起手式与验证
/*
* 题目:组合总和与回溯剪枝
* 要求:输入可含重复值的正整数数组 candidates 和非负整数 target;每个数组位置最多选一次,返回和为 target 的所有不重复组合。每组内升序,组合列表按字典序排列;target=0 返回 [[]],且不得修改输入。
* 示例:candidates=[1,1,2]、target=2 返回 [[1,1],[2]];两个值为 1 的位置可一起选,但相同数值组合只返回一次。
*/
function combinationSumOnce(candidates, target) {}
console.log(combinationSumOnce([10, 1, 2, 7, 6, 1, 5], 8)); // 预期:[ [1, 1, 6], [1, 2, 5], [1, 7], [2, 6], ]
console.log(combinationSumOnce([1, 1, 1], 2)); // 预期:[[1, 1]]
console.log(combinationSumOnce([2], 1)); // 预期:[]
console.log(combinationSumOnce([], 0)); // 预期:[[]]
/*
* 题目:组合总和与回溯剪枝
* 要求:输入可含重复值的正整数数组 candidates 和非负整数 target;每个数组位置最多选一次,返回和为 target 的所有不重复组合。每组内升序,组合列表按字典序排列;target=0 返回 [[]],且不得修改输入。
* 示例:candidates=[1,1,2]、target=2 返回 [[1,1],[2]];两个值为 1 的位置可一起选,但相同数值组合只返回一次。
*/
function combinationSumOnce(candidates, target) {}
console.log(combinationSumOnce([10, 1, 2, 7, 6, 1, 5], 8)); // 预期:[ [1, 1, 6], [1, 2, 5], [1, 7], [2, 6], ]
console.log(combinationSumOnce([1, 1, 1], 2)); // 预期:[[1, 1]]
console.log(combinationSumOnce([2], 1)); // 预期:[]
console.log(combinationSumOnce([], 0)); // 预期:[[]]解题思路与实现
展开解题思路
排序后按索引向后搜索;相同值只在同一层跳过,超过剩余目标时剪枝。
function combinationSumOnce(candidates, target) {
if (!Number.isInteger(target) || target < 0)
throw new RangeError("target must be a non-negative integer");
if (!candidates.every((value) => Number.isInteger(value) && value > 0))
throw new RangeError("candidates must be positive integers");
const values = [...candidates].sort((a, b) => a - b);
const results = [];
const path = [];
function dfs(start, remaining) {
if (remaining === 0) {
results.push([...path]);
return;
}
for (let i = start; i < values.length; i += 1) {
if (i > start && values[i] === values[i - 1]) continue;
if (values[i] > remaining) break;
path.push(values[i]);
dfs(i + 1, remaining - values[i]);
path.pop();
}
}
dfs(0, target);
return results;
}
// 三组以上可直接运行的验证。
console.log(combinationSumOnce([10, 1, 2, 7, 6, 1, 5], 8)); // 预期:[ [1, 1, 6], [1, 2, 5], [1, 7], [2, 6], ]
console.log(combinationSumOnce([1, 1, 1], 2)); // 预期:[[1, 1]]
console.log(combinationSumOnce([2], 1)); // 预期:[]
console.log(combinationSumOnce([], 0)); // 预期:[[]]
function combinationSumOnce(candidates, target) {
if (!Number.isInteger(target) || target < 0)
throw new RangeError("target must be a non-negative integer");
if (!candidates.every((value) => Number.isInteger(value) && value > 0))
throw new RangeError("candidates must be positive integers");
const values = [...candidates].sort((a, b) => a - b);
const results = [];
const path = [];
function dfs(start, remaining) {
if (remaining === 0) {
results.push([...path]);
return;
}
for (let i = start; i < values.length; i += 1) {
if (i > start && values[i] === values[i - 1]) continue;
if (values[i] > remaining) break;
path.push(values[i]);
dfs(i + 1, remaining - values[i]);
path.pop();
}
}
dfs(0, target);
return results;
}
// 三组以上可直接运行的验证。
console.log(combinationSumOnce([10, 1, 2, 7, 6, 1, 5], 8)); // 预期:[ [1, 1, 6], [1, 2, 5], [1, 7], [2, 6], ]
console.log(combinationSumOnce([1, 1, 1], 2)); // 预期:[[1, 1]]
console.log(combinationSumOnce([2], 1)); // 预期:[]
console.log(combinationSumOnce([], 0)); // 预期:[[]]
时间复杂度:最坏 O(n·2^n)。回溯最多枚举指数数量的选择路径,复制答案再乘路径长度。
空间复杂度:O(n),不计输出。排序副本与递归路径至多包含 n 个元素;所有结果另占输出空间。