给定一个无重复元素的数组 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]
]
咋一看这题,还挺简单,扳扳手指头就算出来了
要用代码写出这题,还真是有点不简单。。。
分析思路:
用 目标数 - 列表中的任意数, 然后判断结果是否等于0即可
假设
candidates = [2,3,6,7]
target = 7
黑色:继续下一轮计算
红色:目标数据
蓝色:剔除数据
轮次 | 2 | 3 | 6 | 7 |
---|---|---|---|---|
A | 7-2=5 | 7-3=4 | 7-6=1 | 7-7=0 |
因为可以被重复选取
轮次 | 2 | 3 | 6 | 7 |
---|---|---|---|---|
A-2 | 5-2=3 | 5-3=2 | 5-6=-1 | 5-7=-2 |
A-3 | 4-2=2 | 4-3=1 | 4-6=-2 | 4-7=-3 |
A-6 | 1-2=-1 | 1-3=-2 | 1-6=-5 | 1-7=-6 |
凡是计算结果大于0的,继续下一轮计算
轮次 | 2 | 3 | 6 | 7 |
---|---|---|---|---|
A-2-2 | 3-2=1 | 3-3=0 | 3-6=-3 | 3-7=-4 |
A-2-3/A-3-2 | 2-2=0 | 2-3=-1 | 2-6=-4 | 2-7=-5 |
A-3-3 | 1-2=-1 | 1-3=-2 | 1-6=-5 | 1-7=-6 |
最后一轮
轮次 | 2 | 3 | 6 | 7 |
---|---|---|---|---|
A-2-2-2 | 1-2=-1 | 1-3=-2 | 1-6=-5 | 1-7=-6 |
结束~
通过红色轨迹标记,得到最终结果:
[7]
[2, 2, 3]
[2, 3, 2]
[3, 2, 2]
排序踢重之后结果
[7]
[2, 2, 3]
通过上面的分析,大家应该可以看出一些规律,有想法的小伙伴,可以不看最终代码,自己尝试写个看看?
完整版代码逻辑
import java.util.*;
/**
* @author yanghao
* @version LeetCodeTest.java, v 0.1 2019-10-11 19:13
*/
public class LeetCodeTest {
public static void main(String[] args){
LeetCodeTest test = new LeetCodeTest();
int[] candidates = new int[]{7,3,2};
int target = 18;
System.out.println(test.leetCode39(candidates, target));
}
public List<List<Integer>> leetCode39(int[] candidates, int target) {
//排序
Arrays.sort(candidates);
List<Integer> childList;
List<List<Integer>> list = new ArrayList<>();
//本轮要循环的数据
Map<String, Integer> oldMap = new HashMap<>();
//下一轮要循环的数据,下一轮开始之前,把数据赋值给oldMap,并清空
Map<String, Integer> newMap = new HashMap<>();
//第一轮
for(int first : candidates){
if(target - first == 0){
//存放起来(对应列表红色数据)
childList = new ArrayList<>();
childList.add(first);
list.add(childList);
}else if(target - first > 0){
//放到下一轮(对应列表黑色数据)
oldMap.put(first + "", target - first);
}
}
//第二轮 到 结束
while (oldMap.size() > 0){
//每一轮循环结果大于0的
for(Map.Entry<String, Integer> entry : oldMap.entrySet()) {
//依次递减
for(int index : candidates){
target = entry.getValue() - index;
if(target == 0){
//存放起来(对应列表红色数据)
childList = splitInt(entry.getKey() + "_" + index);
list.add(childList);
}else if(target > 0){
//放到下一轮(对应列表黑色数据)
newMap.put(entry.getKey() + "_" + index, target);
}else{
//因为排过序,遇到小于0之后,后面的减法都会小于0,就可以结束本次循环了
break;
}
}
}
//newMap --> oldMap
oldMap.clear();
oldMap.putAll(newMap);
newMap.clear();
}
//踢重
Set<List<Integer>> setList = new HashSet<>(list);
list = new ArrayList<>(setList);
return list;
}
/**
* 1_2_3 --> [1,2,3]
*/
private List<Integer> splitInt(String key){
String[] strs = key.split("_");
Integer[] ints = new Integer[strs.length];
for(int i = 0; i < strs.length; i ++){
ints[i] = Integer.valueOf(strs[i]);
}
//排序,方便后面踢重
Arrays.sort(ints);
List<Integer> list = Arrays.asList(ints);
return list;
}
}
运行结果:
[[2, 2, 2, 2, 3, 7], [3, 3, 3, 3, 3, 3], [2, 2, 2, 3, 3, 3, 3], [2, 2, 2, 2, 2, 2, 3, 3], [2, 2, 7, 7], [2, 3, 3, 3, 7], [2, 2, 2, 2, 2, 2, 2, 2, 2]]
此代码用了最常规思路去写的,效率和内存消耗都处于中等水平,还可以通过递归的代码去实现,小伙伴们可以自己去研究一下~