动态规划—377. 组合总和 Ⅳ
题目描述
前言
组合总和 IV 是一个经典的动态规划问题。给定一个由 正整数 组成的数组 nums
和一个目标值 target
,求出可以使用 nums
中的数字通过排列得到目标值 target
的不同组合的数量。数组中的数字可以多次重复使用。
与其他类似的组合问题不同的是,排列的顺序会影响结果。也就是说,对于相同的数字组合,排列不同会被视为不同的组合。
基本思路
1. 问题定义
给定一个正整数数组 nums
和一个目标值 target
,求出用 nums
中的数字能组合出目标 target
的不同排列的数量。
举例:
- 输入:
nums = [1, 2, 3]
,target = 4
- 输出:
7
- 解释:有 7 种组合可以凑成 4:
[1, 1, 1, 1]
[1, 1, 2]
[1, 2, 1]
[2, 1, 1]
[2, 2]
[1, 3]
[3, 1]
2. 理解问题和递推关系
动态规划思想:
我们可以使用动态规划来解决这个问题。类似于 背包问题,每个数字都可以无限制地使用,但这里的组合顺序会影响结果。
状态定义:
dp[i]
表示数字i
的组合总数。
状态转移方程:
- 对于每个数字
i
,我们可以枚举nums
中的每个数字num
,如果num <= i
,那么我们可以通过dp[i - num]
来更新dp[i]
,即:
d p [ i ] + = d p [ i − n u m ] dp[i] += dp[i - num] dp[i]+=dp[i−num]dp[i - num]
表示用数字num
凑成i
之前已经凑成i - num
的组合数。
边界条件:
dp[0] = 1
,表示目标值为 0 的组合数只有 1 种,即不选任何数字。
3. 解决方法
动态规划方法
- 初始化:创建一个数组
dp
,dp[i]
表示可以通过排列组合得到目标值i
的组合数,dp[0] = 1
。 - 状态转移:对于每一个小于等于目标值的
i
,遍历nums
中的每个数字num
,更新dp[i]
。 - 返回结果:
dp[target]
即为最终的组合数量。
伪代码:
initialize dp array with dp[0] = 1
for i from 1 to target:
for each num in nums:
if num <= i:
dp[i] += dp[i - num]
return dp[target]
4. 进一步优化
- 时间复杂度:时间复杂度为
O(target * n)
,其中n
是nums
数组的长度,target
是目标值。 - 空间复杂度:空间复杂度为
O(target)
,因为我们需要存储大小为target + 1
的dp
数组。
5. 小总结
- 递推思路:通过递推
dp
数组,逐步累积到目标值target
。排列的顺序影响最终结果,因此对于每个i
,都要考虑所有可能的排列情况。 - 时间复杂度:时间复杂度为
O(target * n)
,适合处理中等规模的输入。
以上就是组合总和 Ⅳ 问题的基本思路。
Python代码
class Solution:
def combinationSum4(self, nums: list[int], target: int) -> int:
# 初始化dp数组,dp[i]表示凑成金额i的排列组合数
dp = [0] * (target + 1)
dp[0] = 1 # 凑成金额0的方式有1种(不选任何数字)
# 动态规划计算
for i in range(1, target + 1):
for num in nums:
if num <= i:
dp[i] += dp[i - num]
return dp[target] # 返回凑成目标target的排列组合数
Python代码解释
- 初始化:定义
dp
数组,dp[i]
表示凑成金额i
的排列组合数,初始时dp[0] = 1
。 - 动态规划递推:遍历每个目标值
i
,然后遍历nums
数组,检查每个数字是否可以组合成i
。 - 返回结果:最终返回
dp[target]
,即为凑成target
的排列组合数。
C++代码
class Solution {
public:
int combinationSum4(vector<int>& nums, int target) {
// 使用 long long 来避免溢出
vector<long long> dp(target + 1, 0);
dp[0] = 1; // 凑成金额0的方式有1种(不选任何数字)
// 动态规划计算
for (int i = 1; i <= target; ++i) {
for (int num : nums) {
if (num <= i) {
dp[i] += dp[i - num];
// 如果 dp[i] 超过 int 范围,停止累加
if (dp[i] > INT_MAX) {
dp[i] = INT_MAX; // 将结果限制在 int 范围内
}
}
}
}
// 返回结果,防止溢出返回大于 INT_MAX 的数值
return dp[target];
}
};
C++代码解释
- 初始化:定义
dp
数组,dp[i]
表示凑成目标值i
的排列组合数,初始时dp[0] = 1
。 - 动态规划递推:遍历每个目标值
i
,然后遍历nums
数组,检查每个数字是否可以组合成i
。 - 返回结果:最终返回
dp[target]
,即为凑成target
的排列组合数。
总结
- 问题核心:本问题是一个典型的动态规划问题,关键在于如何处理每个目标值
i
,并考虑不同排列组合的方式。 - 时间复杂度:时间复杂度为
O(target * n)
,其中n
是nums
数组的长度,target
是目标值。 - 空间复杂度:空间复杂度为
O(target)
,因为我们只需要维护一个dp
数组。