给定一个无重复元素的数组 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的数字可以无限制重复被选取。
说明:
示例 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);
}
}
}
}
执行测试用例后结果如下:
可以看到,这样递归求解,解集中包含了重复的组合。所以自然而然的想到,对求解后的结果集进行去重处理。直观的去重处理的办法是,将列表排序,然后对长度相等的列表逐元素比较,判断列表是否相等,如果等,则最终只保留一个。但是我感觉把所有列表排序效率太低,考虑有没有更有效的去重手段。
考虑的方案一:
对等长列表所有元素做异或运算,以异或的结果判断列表是否相等。相等的列表异或结果一定相等,但不等的列表异或结果就一定不等吗?经过研究后,发现不一定!例如列表 {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)
。这样修改的意思是,递归后,从当前元素的后面开始选数,当前元素只会被选择一次,那么求解的就是不重复选数和等于给定值的问题。