算法:
本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列!
如果本题要把排列都列出来的话,只能使用回溯算法爆搜。
动规五部曲:
1.确定dp及其下标:
dp[i]: 凑成目标正整数为i的排列个数为dp[i]
2.确定递推公式
dp[i](考虑nums[j])可以由 dp[i - nums[j]](不考虑nums[j]) 推导出来。
因为只要得到nums[j],排列个数dp[i - nums[j]],就是dp[i]的一部分。
求装满背包有几种方法,递推公式一般都是:累加
dp[i] += dp[i - nums[j]];
3.dp初始化
dp[0] = 1,仅仅是为了推导递推公式
非0下标的dp[i]初始化为0,这样才不会影响dp[i]累加所有的dp[i - nums[j]]
4.确定遍历顺序
个数可以不限使用,说明这是一个完全背包。
得到的集合是排列,说明需要考虑元素之间的顺序。
如果求组合数就是外层for循环遍历物品,内层for遍历背包(组合:外物;组外-主外)。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
如果把遍历nums(物品)放在外循环,遍历target的作为内循环的话,举一个例子:
计算dp[4]的时候,结果集只有 {1,3} 这样的集合,不会有{3,1}这样的集合,因为nums遍历放在外层,3只能出现在1后面!
所以本题遍历顺序最终遍历顺序:
target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
5.举例推导dp
正确代码:
class Solution {
public int combinationSum4(int[] nums, int target) {
int dp[] = new int[target+1];
dp[0]=1;
for(int i=0; i<=target;i++){
for(int j=0;j<nums.length;j++){
if (i >= nums[j]){
dp[i] += dp[i-nums[j]];
}
}
}
return dp[target];
}
}
注意:
1.写递推公式的时候,要加上:
if (i >= nums[j]){
dp[i] += dp[i-nums[j]];
}
不加的话,会报错,因为 dp[i-nums[j]]的索引 i-nums[j] 一定≥0
时间空间复杂度:
- 时间复杂度: O(target × n),其中 n 为 nums 的长度
- 空间复杂度: O(target)