题目描述
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
- 所有数字(包括目标数)都是正整数。
- 解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
[1,2,2],
[5]
]
方法一 不排序使用哈希表+dfs递归
- 建立哈希表,统计每个数字出现次数
- dfs递归剪枝,target减去当前数字,哈希次数减一,进入下一层
- target==0,找到目标组合,target<0退出
var combinationSum2 = function(candidates, target) {
let _hash = {}, ret = [];
// build hash map
for (let n of candidates) {
_hash[n] = typeof _hash[n]=='undefined' ? 1 : _hash[n]+1;
}
function dfs(hash, target, solution) {
if (target == 0) {
// get it
ret.push(Object.assign([], solution));
return true;
}
if (target < 0) {
return false;
}
for (let k in hash) {
if (hash[k] == 0) {continue;}
solution.push(k);
hash[k]--;
dfs(Object.assign({}, hash), target - k, solution);
// 避免重复,非常关键
delete hash[k];
solution.pop();
}
}
dfs(_hash, target, []);
return ret;
}
ac之后意外的用时击败了5%的用户,内存击败5%用户,大概这应该算是一种暴力求解,所以速度这么慢?
方法二 排序栈+回溯
这是我第一次ac的方法,也是从我39题的基础上直接修改的
基本思路没有区别,只是在一些边界上稍作变化:
var combinationSum2 = function(candidates, target) {
// sort + stack
candidates.sort();
let ret = [],stack = [], i = candidates.length-1;
while (i>=0 || stack.length>0) {
if (target == 0) {
// resolved
let solution = [];
for (var k in stack) {
solution.push(candidates[stack[k]]);
}
ret.push(solution);
let pre = stack.pop();
target += candidates[pre];
// 区别1,找到题解,跳过相同数字,避免重复答案
while (candidates[i] == candidates[pre]){i--;};
continue;
} else if (i < 0) {
pre = stack.pop();
target+=candidates[pre];
i = pre - 1;
// 区别2,索引溢出,本次寻找失败,跳过相同的数字
while (i>0 && candidates[i] == candidates[pre]) {i--;}
continue;
} else if (target >= candidates[i]) {
// 区别3,入栈之后索引需要移位
stack.push(i);
target -= candidates[i];
}
i--;
}
//
return ret;
};