题目:
我们利用 打表法来找出转移方程:
方阵中每一个格子的值计算过程如下所示:
行表示硬币的面值,列表示金额amnout的数量;
我们对首先对每个格子的初始化值都为amnout + 1;
每一行表示目前只能取到0到i个硬币,后面面值的硬币先不考虑;
对于dp[i][j](即每个格的值所代表的含义:表示在凑出当前金额j所需要0到i个硬币的最小数量;
很容易理解,我们的第一列值都应该为0;凑出金额0需要0枚硬币;
下面我们手动计算一下第二行,即目前只能取到面值为0和面值为1的硬币;
手动计算第三行:
在计算的过程中,我们可以得出状态转移方程:
if j >= coins[i -1]:
dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1)
else:
dp[i][j] = dp[i -1][j]
因此,二维dp代码如下:
class Solution:
def coinChange(self, coins, amount):
dp = [[amount + 1] * (amount + 1) for i in range(len(coins) + 1)]
for i in range(len(coins) + 1):
dp[i][0] = 0
# print(dp)
for i in range(1,len(coins) + 1):
for j in range(0, amount + 1):
if j >= coins[i -1]:
dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1)
else:
dp[i][j] = dp[i -1][j]
if dp[len(coins)][amount] <= amount:
return dp[len(coins)][amount]
else:
return -1
下面进行简化为一维dp:
这时候如果我们理解了完全背包问题,就很容易理解二维到一维dp的压缩过程
二维的状态转移方程:
if j >= coins[i -1]:
dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1)
else:
dp[i][j] = dp[i -1][j]
如果压缩为一维度,我们可以把第一维度压缩了,即变成如下的方程:
dp[j] = min(dp[j], dp[j - coins[i - 1]] + 1])
变成上述形式其实就是一个先后计算的问题(并且也关系到硬币数量可以取无限次的情况)
下面我们认真分析:
在未更新dp[j](dp[i][j])之前,dp[j]表示dp[i - 1][j]
因此,dp[i - 1][j] 可以表示为dp[j]
而dp[i][j - coins[i - 1]]中j - coins[i - 1]的值是小于j的,因此是已经计算(更新)过的
所以,dp[i][j - coins[i - 1]]可以表示为dp[j - coins[i - 1]]
因此,状态转移方程如下所示:
dp[j] = min(dp[j], dp[j - coins[i - 1]] + 1])
一维dp代码如下所示:
class Solution:
def coinChange(self, coins, amount):
#一维dp
dp = [amount + 1] * (amount + 1)
dp[0] = 0
for i in range(1, len(coins) + 1):
for j in range(0, amount + 1):
if j >= coins[i - 1]:
dp[j] = min(dp[j], dp[j - coins[i - 1]] + 1)
if dp[amount] <= amount:
return dp[amount]
else:
return -1