leetcode 39. 组合总和(Java版)

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

给定一个无重复元素的数组 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);
        	}
        }
    }
}

执行测试用例后结果如下:
leetcode 39. 组合总和(Java版)_第1张图片
可以看到,这样递归求解,解集中包含了重复的组合。所以自然而然的想到,对求解后的结果集进行去重处理。直观的去重处理的办法是,将列表排序,然后对长度相等的列表逐元素比较,判断列表是否相等,如果等,则最终只保留一个。但是我感觉把所有列表排序效率太低,考虑有没有更有效的去重手段。
考虑的方案一:
对等长列表所有元素做异或运算,以异或的结果判断列表是否相等。相等的列表异或结果一定相等,但不等的列表异或结果就一定不等吗?经过研究后,发现不一定!例如列表 {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,Java)