一、题目内容 中等
给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。
示例1:
输入:nums = [1,2,3], target = 4 输出:7 解释: 所有可能的组合为: (1, 1, 1, 1) (1, 1, 2) (1, 2, 1) (1, 3) (2, 1, 1) (2, 2) (3, 1) 请注意,顺序不同的序列被视作不同的组合。
示例2:
输入:nums = [9], target = 3 输出:0
提示:
- 1 <= nums.length <= 200
- 1 <= nums[i] <= 1000
- nums 中的所有元素 互不相同
- 1 <= target <= 1000
进阶:如果给定的数组中含有负数会发生什么?问题会产生何种变化?如果允许负数出现,需要向题目中添加哪些限制条件?
二、解题思路
:::warning 本题题目描述说是求组合,但又说是可以元素相同顺序不同的组合算两个组合,其实就是求排列! :::
但其本质是本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来。 如果本题要把排列都列出来的话,只能使用回溯算法爆搜。
动规五部曲分析如下:
- 确定dp数组以及下标的含义
**dp[i]**
: 凑成目标正整数为 i 的排列个数为**dp[i]**
- 确定递推公式
dp[i]
可以由 dp[i - nums[j]]
推导出来。
因为只要得到nums[j]
,排列个数dp[i - nums[j]]
,就是dp[i]
的一部分
- dp数组如何初始化
因为递推公式dp[i] += dp[i - nums[j]]
的缘故,dp[0]
要初始化为 1,这样递归其他dp[i]
的时候才会有数值基础。
- 确定遍历顺序
个数可以不限使用,说明这是一个完全背包。
得到的集合是排列,说明需要考虑元素之间的顺序。
本题要求的是排列,那么这个 for 循环嵌套的顺序可以有说法了。
:::info
如果求组合数就是外层 for 循环遍历物品,内层 for 遍历背包。
如果求排列数就是外层 for 遍历背包,内层 for 循环遍历物品。
:::
如果把遍历 nums(物品)放在外循环,遍历 target 的作为内循环的话,举一个例子:计算dp[4]
的时候,结果集只有 {1,3}
这样的集合,不会有{3,1}
这样的集合,因为 nums 遍历放在外层,3 只能出现在 1 后面!
所以本题遍历顺序最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。
- 举例来推导 dp 数组
三、具体代码
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var combinationSum4 = function (nums, target) {
const dp = new Array(target + 1).fill(0)
dp[0] = 1
for (let j = 0; j <= target; j++) {
for (let i = 0; i < nums.length; i++) {
if (j >= nums[i]) dp[j] += (dp[j - nums[i]] || 0)
}
}
return dp[target]
};