CoinChange问题
CoinChange问题是很经典的问题,在leetcode上有518. Coin Change 2 h和322. Coin Change 。其中:
- Coin Change 2:给定固定有限种类
a1,a2,...,am
的硬币,求换成金额
n
的方法数,即求不定方程
∑mi=1aiki=n 的非负解的个数。 - Coin Change:上述问题的变体,类似完全背包问题,一个最优化问题:
min∑i=1mkis.t.a1k1+a2k2+...+amkm=n
下面将主要以Coin Change 2来讨论,首先想到的是直接从解的角度出发:
不定方程的解角度
首先看的是解的存在性,对于不定方程 ∑mi=1aiki=n 的解的存在性可以由下定理保证:(证明在这里)
定理1:不定方程 ∑mi=1aiki=n 存在解的充要条件是 gcd(a1,a2,...,am)|n .
实际上,从不定方程的解的角度入手是基本上解决不了本问题的,以二元一次不定方程为例,即
ax+by=n
,假设它有特解
(x0,y0)
,那么方程的解有形式:
这样,非负整数解一下就可以求出来了,但是问题是要首先得到特解,多元一次不定方程的解也有类似的形式,而特解没有足够好的办法得到,所以这种方法不太好。
母函数角度
高中时代就看过一种巧妙的组合大法,甚至有某科大巨犇说:一切组合问题都是可以由母函数生成。我一脸懵逼地深以为然。以
x+2y+5z=5
为例,设:
那么我们要求的方法总数即为多项式 f1⋅f2⋅f3 中 x5 项的系数,稍微想一想就知道这个操作的准确性,如果我们有强大的工具如MATLAB,那么我们可以考虑 f1⋅f2⋅f3 在0处的Maclaurin展开,处理一下:
然后剩下的就靠MATLAB求导数什么的了,稳。但白手起家的编程的话,略繁。还有一些类似问题的思路可以看 这里。
DP角度
下面依然会以
x+2y+5z=5
为例,用
dp(i,j)
表示目标金额为
i
,且用前
作图所示为整个过程的示意图,其中最后的 dp(5,5) 即为所求,代码如下:
class Solution(object):
def change(self, amount, coins):
dp=[[0]*(len(coins)+1) for x in range(amount+1)]
#初始化
for y in range(amount+1):
dp[y][0]=0
for x in range(len(coins)+1):
dp[0][x]=1
#dp[i][j]=dp[i][j-1]+dp[i-conins[j]][j]
for i in range(amount):
for j in range(len(coins)):
tempi=i+1
tempj=j+1
if tempi>=coins[tempj-1]:
dp[tempi][tempj]=dp[tempi][tempj-1]+dp[tempi-coins[tempj-1]][tempj]
else:
dp[tempi][tempj]=dp[tempi][tempj-1]
return dp[-1][-1]
实际上这样的方法是灰常消耗空间的,注意到实际上,每次进行状态转移的时候,对于某个元,我们需要的元素是左边一格的元素和上方的一个元素,所以说我们完全可以一列一列地计算,而仅仅用一个一维数组即可,计算时间大大减少(虽然时间复杂度相同),红框用的是下方代码,蓝框用的是上方代码。代码如下:
class Solution(object):
def change(self, amount, coins):
dp = [0] * (amount + 1)
dp[0] = 1
for c in coins:
for x in range(c, amount + 1):
dp[x] += dp[x - c]
return dp[amount]
关于 Coin Change问题
这部分的内容将在下章中具体探讨,包括与背包问题的关系,算法上探讨一下BFS,DFS和各种启发式算法。下面将先给出其状态转移方程:
过程如上右图所示,扔下代码就跑:
class Solution(object):
def coinChange(self, coins, amount):
temp=233333333333333333333
dp=[[0]*(len(coins)+1) for x in range(amount+1)]
for x in range(amount+1):
dp[x][0]=temp
for i in range(amount):
for j in range(len(coins)):
tempi=i+1
tempj=j+1
if tempi>=coins[tempj-1]:
dp[tempi][tempj]=min(dp[tempi][tempj-1],1+dp[tempi-coins[tempj-1]][tempj])
else:
dp[tempi][tempj]=dp[tempi][tempj-1]
if dp[-1][-1]==temp:
return -1
return dp[-1][-1]
或者用下面这更为高效的代码(一维数组杀)
class Solution(object):
def coinChange(self, coins, amount):
dp=[233333333]*(amount+1)
dp[0]=0
for c in coins:
for i in range(c,len(dp)):
dp[i]=min(dp[i],dp[i-c]+1)
if dp[-1]==233333333:
return -1
return dp[-1]
敬请期待:CoinChange问题(下)——背包问题