"又见背包,让我不再走得缓慢"
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第13天,点击查看活动详情
377. 组合总和 Ⅳ 题目描述:给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
| 示例 |
|---|
| 输入:, 输出: 解释:所有可能的组合为: (1, 1, 1, 1)、(1, 1, 2)、(1, 2, 1)、(1, 3)、(2, 1, 1)、(2, 2)、(3, 1)。 请注意,顺序不同的序列被视作不同的组合。 |
乍一看本题和 # 518. 零钱兑换 II 如出一辙,但是细看示例才发现,题目提到的“组合”个数其实是“排列”个数,所以这道题其实是 完全背包 问题下的 排列 问题。
中规中矩的动态规划
1、确定 dp 状态数组
定义 在 区间内选择元素时,凑成目标整数为 的排列个数,其中 ,, 。
2、确定 dp 状态方程
当 放弃 元素时,则有 ,此时
当 选择 元素时,则有 ,此时
NOTE:
为何是 而不是 ?因为 方向上有一个“哨兵”,即 在 方向上比 多了一个元素。
为何是 而不是 ?因为这是 排列 问题, 计算出来的依然是组合个数,而非排列。
3、确定 dp 初始状态
对于任意 ,均存在 ,即背包容量为 ,总有一种选择方法(什么物品都不选)。
4、确定遍历顺序
完全背包的排列问题,先遍历背包,再遍历物品,故
-
外循环,从 遍历到
-
内循环,从 遍历到
5、确定最终返回值
依然要回归到 状态定义中,即 在 区间内选择元素时,凑成目标整数为 的排列个数。
6、代码示例
/**
* 空间复杂度 O(target * nums.length)
* 时间复杂度 O(target * nums.length)
*/
function combinationSum4(nums: number[], target: number): number {
const n = nums.length;
const dp = Array.from({ length: n + 1}, () => new Array(target + 1).fill(0));
for (let i = 0; i <= n; i++) {
dp[i][0] = 1;
}
for (let j = 1; j <= target; j++) {
for (let i = 1; i <= n; i++) {
if (j - nums[i - 1] < 0) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j] + dp[n][j - nums[i - 1]];
}
}
}
return dp[n][target];
};
思维提升-空间压缩
1、确定 dp 状态数组
定义 是凑成目标整数为 的排列个数,其中 。
2、确定 dp 状态方程
完全背包问题一维 模型(参考 # 重识背包问题(下))应为,
其中,
🎡 NOTE: 当 时,继续循环即可,无须额外计算。
3、确定 dp 初始状态
当目标整数为 时,即 ,能凑成 的排列个数一定为 ,即不选择任何元素。(因为题目规定了任意 ,所以这里才能直接赋值为 )。
4、确定遍历顺序
完全背包的排列问题,必须先遍历背包,再遍历物品,故
-
外循环,从 遍历到
-
内循环,从 遍历到
5、确定最终返回值
依然要回归到 状态定义中,即 是凑成 的排列个数。
6、代码示例
/**
* 空间复杂度 O(target)
* 时间复杂度 O(target * nums.length)
*/
function combinationSum4(nums: number[], target: number): number {
const n = nums.length;
const dp = new Array(target + 1).fill(0);
dp[0] = 1;
for (let j = 1; j <= target; j++) {
for (let i = 0; i < n; i++) {
if (j - nums[i] >= 0) {
dp[j] += dp[j - nums[i]];
}
}
}
return dp[target];
};