美文网首页
leetcode 39. 组合总和(Java版)

leetcode 39. 组合总和(Java版)

作者: M_lear | 来源:发表于2019-06-14 21:54 被阅读0次

    题目描述(题目难度,中等)

    给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
    candidates 中的数字可以无限制重复被选取。
    说明:

    • 所有数字(包括 target)都是正整数。
    • 解集不能包含重复的组合。

    示例 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]
    ]
    

    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/combination-sum
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    题目求解

    一时没有想到什么巧妙的解法,考虑用递归求解。
    凭感觉快速写了一个递归求解的版本:
    代码如下:

    class Solution {
        public List<List<Integer>> combinationSum(int[] candidates, int target) {
            List<List<Integer>> result = new ArrayList<>();
            comSum(candidates, target, result, new ArrayList<>());
            return result;
        }
        void comSum(int[] can, int tar, List<List<Integer>> res, ArrayList<Integer> canRes){
            if(tar == 0){
                res.add(new ArrayList<>(canRes));
                return;
            }
            if(tar < 0) return;
            for(int i = 0; i < can.length; ++i){            
                if(can[i] <= tar){
                    canRes.add(can[i]);
                    comSum(can, tar-can[i], res, canRes);
                    canRes.remove(canRes.size()-1);
                }
            }
        }
    }
    

    执行测试用例后结果如下:


    截图20190614205027.png

    可以看到,这样递归求解,解集中包含了重复的组合。所以自然而然的想到,对求解后的结果集进行去重处理。直观的去重处理的办法是,将列表排序,然后对长度相等的列表逐元素比较,判断列表是否相等,如果等,则最终只保留一个。但是我感觉把所有列表排序效率太低,考虑有没有更有效的去重手段。
    考虑的方案一:
    对等长列表所有元素做异或运算,以异或的结果判断列表是否相等。相等的列表异或结果一定相等,但不等的列表异或结果就一定不等吗?经过研究后,发现不一定!例如列表 {12(1100), 3(0011)} 和列表 {10(1010), 5(0101)},是两个和都为 15 的不同列表,但异或结果相同,也都是 15。
    考虑的方案二:
    对等长列表所有元素做累乘运算,以乘积结果判断列表是否相等。理论上感觉可行,编程实现后,发现还是不行,提交代码得到了一个反例 {3, 8, 10},{4, 5, 12},和都为 21,积都为 240。

    思来想去,想不出什么去重的好办法。最后感觉这个题不应该这样解,这样解既复杂又不直观。考虑改进递归,观察上一版本代码的输出,想到在递归选数的过程中,能不能不选择已选数字之前的数字。例如对于输入 {2, 3, 6, 7},在选择 3 之后,就不再选择 2,那么就不会出现像 {2, 3, 2},{3, 2, 2} 这样的重复组合了。
    按上述思路改进后的递归代码如下:

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        comSum(candidates, target, result, new ArrayList<>(), 0);
        return result;
    }
    void comSum(int[] can, int tar, List<List<Integer>> res, ArrayList<Integer> canRes, int low){
        if(tar == 0){
            res.add(new ArrayList<>(canRes));
            return;
        }
        if(tar < 0) return;
        for(int i = low; i < can.length; ++i){          
            if(can[i] <= tar){
                canRes.add(can[i]);
                comSum(can, tar-can[i], res, canRes, i);
                canRes.remove(canRes.size()-1);
            }
        }
    }
    

    给递归函数增加了一个参数,控制选数的起始位置。代码提交后,运行通过。
    另外,有意思的是,如果把递归调用的代码 comSum(can, tar-can[i], res, canRes, i) 的最后一个实参稍作修改,改为 comSum(can, tar-can[i], res, canRes, i+1)。这样修改的意思是,递归后,从当前元素的后面开始选数,当前元素只会被选择一次,那么求解的就是不重复选数和等于给定值的问题。

    相关文章

      网友评论

          本文标题:leetcode 39. 组合总和(Java版)

          本文链接:https://www.haomeiwen.com/subject/oormfctx.html