已知数组C和目标值T,要求在C中找出所有独特的组合使其和为T,C中所有的数和T都为正整数
39. Combination Sum
Note:
1. C中无重复
2. 同一个数在组合中可以出现任意次
比如C=[2, 3, 6, 7] ,T=7, 解为
[
[7],
[2, 2, 3]
]
解法
简单回溯
public class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
findCombination(result, new ArrayList<Integer>(), candidates, target, 0);
return result;
}
public void findCombination(List<List<Integer>> result, List<Integer> combinations, int[] candidates, int remain, int start){
if(remain < 0) return;
if (remain == 0){
result.add(new ArrayList<Integer>(combinations));
}
//每递归一层for循环确定一个位置的数,如第k层for循环就确定第k个数
for(int i = start; i < candidates.length; i++) {
combinations.add(candidates[i]);
findCombination(result, combinations, candidates, remain - candidates[i], i);//start置为i表明允许多次使用同一个数
combinations.remove(combinations.size()-1);
}
}
}
简单回溯 + 排序优化
public class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
//排序优化
Arrays.sort(candidates);
findCombination(result, new ArrayList<Integer>(), candidates, target, 0);
return result;
}
public void findCombination(List<List<Integer>> result, List<Integer> combinations, int[] candidates, int remain, int start){
if(remain < 0) return;
if (remain == 0){
result.add(new ArrayList<Integer>(combinations));
}
//每递归一层for循环确定一个位置的数,如第k层for循环就确定第k个数
for(int i = start; i < candidates.length && candidates[i] <= remain; i++) { //当c[i]大于remain,则i以及i以后的都不会符合
combinations.add(candidates[i]);
findCombination(result, combinations, candidates, remain - candidates[i], i);
combinations.remove(combinations.size()-1);
}
}
}
40. Combination Sum II
Note:
1. C中可能有重复值
2. C中的每一个数在组合中只能出现一次
如C = [10, 1, 2, 7, 6, 1, 5], T = 8, 解为
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
解法
利用排序去重
public class Solution {
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(candidates);//先排序以便去重
findCombination(result, new ArrayList<Integer>(), candidates, target, 0);
return result;
}
public void findCombination(List<List<Integer>> result, List<Integer> combinations, int[] candidates, int remain, int start){
if(remain < 0) return;
if (remain == 0){
result.add(new ArrayList<Integer>(combinations));
}
//每层for循环确定一个位置的数,如第k层确定第k个数
for(int i = start; i < candidates.length && candidates[i] <= remain; i++) {
//保证当前位置不会重复使用相等的数,如1,1,2,5,6,7,10,位置0只可能使用第一个1,位置1只可能使用第二个1
if(i > start && candidates[i] == candidates[i-1]) continue;
combinations.add(candidates[i]);
findCombination(result, combinations, candidates, remain - candidates[i], i + 1);
combinations.remove(combinations.size()-1);
}
}
}
377. Combination Sum IV
只需求出所有可能组合的数量
Note:
1. C中无重复
2. C中的每一个数在组合中可出现任意次
如C = [1, 2, 3], T = 4, 则
可能的所有组合为
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
故结果为7.
解法
递推公式:comb[target] = sum(comb[target - nums[i]]) (0 <= i < nums.length && target >= nums[i]),comb[0] = 1。
递归,会超时,因为存在的大量重复计算(调用)
public class Solution {
public int combinationSum4(int[] nums, int target) {
if (target == 0) return 1;
int res = 0;
for (int i = 0; i < nums.length; i++) {
if (target >= nums[i]) {
res += combinationSum4(nums, target - nums[i]);
}
}
return res;
}
}
动态规划(从dp[0]到dp[target]都要计算,其中大部分可能都是不必要的)
public class Solution {
public int combinationSum4(int[] nums, int target) {
int [] dp = new int[target + 1];
dp[0] = 1;
for(int i = 1; i <= target; i++){
for(int j = 0; j < nums.length; j++){
dp[i] += (i - nums[j]) < 0 ? 0 : dp[i-nums[j]];
}
}
return dp[target];
}
}
动态规划+递归(可以将dp[]看作缓存),避免了重复计算和不必要的计算
public class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target + 1];
Arrays.fill(dp, -1);
dp[0] = 1;
return helper(nums, target, dp);
}
private int helper(int[] nums, int target, int[] dp) {
//已计算过直接返回
if (dp[target] != -1) return dp[target];
int res = 0;
for (int i = 0; i < nums.length; i++) {
if (target >= nums[i]) {
res += helper(nums, target - nums[i], dp);
}
}
dp[target] = res;
return res;
}
}