题目分析
- 题目给出了一个
target
目标钱数,和一个nums
数组,其中元素代表是硬币的币值- 题目说不同硬币的币值可以无限次挑选
- 题目要求返回凑出
target
目标数的硬币挑选方案的数量
方法一:动态规划
- 实现思路
- 我们规定
dp[i][j]
表示在前i
种硬币中挑选,凑出j
的方案数,最终返回结果是dp[len(nums)][target]
- 因此对于每一个
dp[i][j]
我们知道- 第
i
个硬币选择0个,有dp[i][j] = dp[i-1][j]
- 第
i
个硬币选择1个,有dp[i][j] = dp[i-1][j-nums[i-1]]
- 第
i
个硬币选择k个,有dp[i][j] = dp[i-1][j-k*nums[i-1]]
- 第
- 状态转移方程有
-
初始化条件
dp[0][0]=1
,对于0种硬币凑出0价值,只有一种方案
- 我们规定
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param target int整型
# @param nums int整型一维数组
# @return int整型
#
class Solution:
def change(self , target: int, nums: List[int]) -> int:
# write code here
dp = [[0]*(target+1) for _ in range(len(nums)+1)]
dp[0][0] = 1 # 用前0个元素凑0元有且只有一种方法
for i in range(1, len(nums) + 1):
value = nums[i-1]
for j in range(target + 1):
k = 0 # 挑选value的硬币数量
while k*value <= j:
dp[i][j] += dp[i-1][j-k*value] # 迭代将挑新的硬币数量为0,1,2...都统计起来
k += 1
return dp[len(nums)][target]
复杂度分析
- 时间复杂度:,其中是目标凑出来的值,是硬币数组的总长度
- 空间复杂度:,其中是目标凑出来的值,是硬币数组的总长度
方法二:动态规划+时间优化
- 实现思路
- 由于上一个动态规划中,我们有一部分代价在迭代相加第
i
种硬币选择k
数量的情况,这造成了很大的时间开销 - 我们可以发现
dp[i][j-nums[i-1]]
就能代表所有的相加之和的部分。具体理解起来就是,由于我们的硬币挑选可以无限次,所以我们不要直接去找dp[i-1]
的部分,dp[i][j-nums[i-1]]
表示我在挑选了一枚第i
个硬币后,我还可以在前i
个硬币中继续挑选,因此省去了大量将k=1,2,...
相加的工作 - 数学推导如下
dp[i][j] = dp[i-1][j] + dp[i-1][j-v] + dp[i-1][j-2v] + ... + dp[i-1][j-kv])
dp[i][j-v] = dp[i-1,[j-v] + dp[i-1][j-2v] + ... + dp[i-1][j-kv])
- 因此
dp[i][j] = dp[i-1][j] + dp[i][j-v]
- 由于上一个动态规划中,我们有一部分代价在迭代相加第
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param target int整型
# @param nums int整型一维数组
# @return int整型
#
class Solution:
def change(self , target: int, nums: List[int]) -> int:
# write code here
dp = [[0]*(target+1) for _ in range(len(nums)+1)]
# 前0,1,2...,len(nums)个元素凑出0的方案为1种
for i in range(len(nums)+1):
dp[i][0] = 1
for i in range(1, len(nums)+1):
value = nums[i-1]
for j in range(1, target+1):
if j >= value:
dp[i][j] = dp[i-1][j] + dp[i][j-value] # dp[i][j-value]表示虽然减去了一次挑选value值硬币的尝试,但是我还可以在前i种硬币中继续挑选,因此解决了可以无限次挑选同一种硬币的问题
else:
dp[i][j] = dp[i-1][j]
return dp[len(nums)][target]
复杂度分析
- 时间复杂度:,其中是目标凑出来的值,是硬币数组的总长度
- 空间复杂度:,其中是目标凑出来的值,是硬币数组的总长度
方法三:动态规划+时间优化+空间优化(滚动数组)
- 实现思路
-
因为我们看到
dp
转移的过程中当前行的状态只跟前一行有关系,因可以用滚动数组的方式减少空间开销 -
将二维数组转化为一维数组,具体实现可以与方法二对比
-
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param target int整型
# @param nums int整型一维数组
# @return int整型
#
class Solution:
def change(self , target: int, nums: List[int]) -> int:
# write code here
dp = [0]*(target+1)
# 前0,1,2...,len(nums)个元素凑出0的方案为1种
dp[0] = 1
for i in range(1, len(nums)+1):
value = nums[i-1]
for j in range(value, target+1):
dp[j] = dp[j] + dp[j-value]
return dp[target]
复杂度分析
- 时间复杂度:,其中是目标凑出来的值,是硬币数组的总长度
- 空间复杂度:,其中是目标凑出来的值,是硬币数组的总长度,二维数组空间代价用一维数组滚动数组代替