[leetcode 40. 组合总和 II] 不排序使用哈希表+dfs递归 vs 排序栈+回溯

题目描述

给定一个数组 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;
};
原文地址:https://www.cnblogs.com/dapianzi/p/12704549.html