题目

题目来源:力扣(LeetCode)

给你一个由 不同 整数组成的数组 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,5)和(5,1)是同一个组合。
排列强调顺序,(1,5)和(5,1)是两个不同的排列。
本题求的是排列总和,而且仅仅是求排列总和的个数,并不是把所有的排列都列出来

1、状态定义
dp[i]:表示凑成目标正整数为 i 的排列个数

2、确定递推公式
假设目标正整数为 i ,当 1 ≤ i ≤ target 时,如果存在一种排列,其中的元素之和等于 i ,则该排列的最后一个元素一定是数组 nums 中的一个元素。假设该排列的最后一个元素是 num,则一定有 num ≤ i ,对于元素之和等于 i - num 的每一种排列,最后添加 num 之后即可得到一个元素之和等于 i 的排列,因此,在计算 dp[i] 时,应该计算所有的 dp[i - num] 之和。

因此,dp[i] (考虑 num 的排列总和) 就是所有的dp[i- num] (不考虑 num)相加。

所以递推公式:dp[i] += dp[i - num]

3、状态初始化
dp[0]=1:只有当不选取任何元素时,元素之和才为 0,因此只有 1 种方案。dp[0]要初始化为1,这样递归其他dp[i]的时候才会有数值基础。其实dp[0] = 1是没有意义的,仅仅是为了推导递推公式。

非0下标的dp[i]应该初始化为0,这样才不会影响dp[i]累加所有的dp[i - nums[j]] 。

4、确定遍历顺序
如果求组合数就是外层for循环遍历物品,内层for遍历背包。
如果求排列数就是外层for遍历背包,内层for循环遍历物品。
本题最终遍历顺序:target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历。

代码实现

  1. /**
  2. * @param {number[]} nums
  3. * @param {number} target
  4. * @return {number}
  5. */
  6. var combinationSum4 = function (nums, target) {
  7. const dp = new Array(target + 1).fill(0);
  8. // dp[0]=1 只有当不选取任何元素时,元素之和才为 0,因此只有 1 种方案。
  9. dp[0] = 1;
  10. // target(背包)放在外循环,将nums(物品)放在内循环,内循环从前到后遍历
  11. for (let i = 1; i <= target; i++) {
  12. for (const num of nums) {
  13. if (num <= i) {
  14. dp[i] += dp[i - num];
  15. }
  16. }
  17. }
  18. return dp[target];
  19. };

参考阅读 https://leetcode-cn.com/problems/combination-sum-iv/solution/zu-he-zong-he-iv-by-leetcode-solution-q8zv/ https://leetcode-cn.com/problems/combination-sum-iv/solution/dai-ma-sui-xiang-lu-377-zu-he-zong-he-iv-pj9s/ https://leetcode-cn.com/problems/combination-sum-iv/solution/yi-pian-wen-zhang-chi-tou-bei-bao-wen-ti-qkgr/