1.题目描述
给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
示例:
nums = [1, 2, 3]
target = 4
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。
因此输出为 7。
2.题意解析
读完题目,这个题目的本质就是一个完全背包问题。但是这个题目的深层意思就是求排列不是 组合,所以在遍历背包的顺序需要与此前不一样,一般完全背包问题,外层循环是遍历物品,内层循环遍历背包的重量。但是这个是题目对物品的顺序有要求。所以在完全背包的顺序就需要特别注意。具体的做法可以参考上一篇文章目标和。具体的解析可以看下面:
本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!
弄清什么是组合,什么是排列很重要。
组合不强调顺序,(1,5)和(5,1)是同一个组合。
排列强调顺序,(1,5)和(5,1)是两个不同的排列。
动态规划五部曲如下:
1.dp数组的定义
dp[i]: 凑成目标正整数为i的排列个数为dp[i].
2.确定dp数组状态转移方程
不考虑nums[j]的情况下,填满容量为j背包,有dp[j]中方法。
那么只要搞到nums[j]的话,凑成j-nums[i]就有dp[j - nums[i]] 种方法。
3.dp数组的初始化
因为递推公式dp[i] += dp[i - nums[j]]的缘故,dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。
至于dp[0] = 1 有没有意义呢?
其实没有意义,所以我也不去强行解释它的意义了,因为题目中也说了:给定目标值是正整数! 所以dp[0] = 1是没有意义的,仅仅是为了推导递推公式。
至于非0下标的dp[i]应该初始为多少呢?
初始化为0,这样才不会影响dp[i]累加所有的dp[i - nums[j]]。
4.确定遍历顺序
- 数可以不限使用,说明这是一个完全背包。
- 得到的集合是排列,说明需要考虑元素之间的顺序。
- 本题要求的是排列,那么这个for循环嵌套的顺序可以有说法了。
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!
所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
5.举例来推导dp数组
3.源代码
public int combinationSum4(int[] nums, int target) {
if (nums == null || nums.length == 0) {
return 0;
}
int[] maximum = new int[target + 1];
maximum[0] = 1;
Arrays.sort(nums);
for (int i = 1; i <= target; i++) {
for (int j = 0; j < nums.length && nums[j] <= i; j++) {
maximum[i] += maximum[i - nums[j]];
}
}
return maximum[target];
}
Leetcode 提交:
回溯法解决代码:
public int combinationSum4_1(int[] nums, int target) {
if (target == 0) {
return 1;
}
int res = 0;
for (int num : nums) {
if (target >= num) {
res += combinationSum4_1(nums, target - num);
}
}
return res;
}
上面是暴力DFS 搜索解决方法,效率不是很高,加上记忆化搜索的代码如下:
class Solution {
int memory[];
public int combinationSum4(int[] nums, int target) {
memory=new int[target+1];
Arrays.fill(memory,-1);
memory[0]=1;
return dfs(nums,target);
}
private int dfs( int []nums,int target){
if(memory[target]!=-1){
return memory[target];
}
int ans=0;
for(int num:nums){
if(target>=num){
ans+=dfs(nums,target-num);
}
}
memory[target]=ans;
return ans;
}
}
Leetcode 提交: