【問題】
給定陣列arr,arr中所有的值都為整數且不重複。每個值代表一種面值的貨幣,每種貨幣有無數張,再給定乙個整數aim代表要找的錢數,求換錢的方法有多少種。
【基本思路】
這道題的經典之處在於它可以體現暴力遞迴、記憶搜尋、動態規劃之間的關係,並可以在動態規劃的基礎上再進行一次優化。
首先介紹暴力遞迴的方法。如果arr = [5, 10, 25, 1],aim = 1000,分析過程如下:
用0張5元的貨幣,讓[10, 25, 1]組成剩下的1000,最終方法數記為res1。
用1張5元的貨幣,讓[10, 25, 1]組成剩下的995,最終方法數記為res2。
用2張5元的貨幣,讓[10, 25, 1]組成剩下的990,最終方法數記為res3。
……用201張5元的貨幣,讓[10, 25, 1]組成剩下的0,最終方法數記為res201。
那麼res1 + res2 + res3 + …… +res201的值就是中的方法數。根據如上的分析過程定義遞迴函式process1(arr, index, aim)它的含義是如果用arr[index..n-1]這些面值的錢組成aim,返回總的方法數。最壞情況下時間複雜度為o(aim^n),n表示陣列的長度。
下面是用python3實現的**
#暴力遞迴方法
defcoins1
(arr, aim):
defprocess1
(arr, index, aim):
if index == len(arr):
return
1if aim == 0
else
0else:
res = 0
for i in range(0, aim//arr[index]+1):
res += process1(arr, index+1, aim-arr[index]*i)
return res
if arr == none
or len(arr) == 0
or aim < 0:
return
0return process1(arr, 0, aim)
記憶搜尋的方法。在暴力遞迴中,有很多的重複計算,比如使用0張5元+1張10元的情況和使用2張5元+0張10元的情況,都需要求[25, 1]組成剩下的990的方法數。記憶搜尋就是使用一張記錄表將遞迴過程中的結果進行記錄,當下次再遇到同樣的遞迴過程,就直接使用表中的資料,這樣就對暴力遞迴進行了優化。
時間複雜度為o(n*aim^2),空間複雜度o(n*aim)。
#記憶搜尋方法
defcoins2
(arr, aim):
defprocess2
(arr, index, aim, records):
if index == len(arr):
return
1if aim == 0
else
0else:
res = 0
for i in range(0, aim//arr[index]+1):
mapvalue = records[index+1][aim-arr[index]*i]
if mapvalue != 0:
res += mapvalue if mapvalue != -1
else
0else:
res += process2(arr, index+1, aim-arr[index]*i, records)
records[index][aim] = -1
if res == 0
else res
return res
if arr == none
or len(arr) == 0
or aim < 0:
return
0 records = [[0
for i in range(aim+1)] for j in range(len(arr)+1)]
return process2(arr, 0, aim, records)
動態規劃的方法。首先生成行數為n、列數為aim+1的dp矩陣,dp[i][j]的含義是在使用arr[0…i]貨幣的前提下,組成錢數j有多少種方法。dp[i][j]的值求法如下:
對於矩陣的第一行,表示只使用貨幣arr[0]的情況下,組成錢的方法數,可以組成的錢數是arr[0]的整倍數,所以將其設定成1
對於矩陣的第一列,表示組成錢數0的方法數,很明顯是1種,也就是不使用任何貨幣,所以第一列都設定為1
對於矩陣的其他位置,dp[i][j]的值來自以下幾個值的累加:完全不使用貨幣arr[i],使用一張貨幣arr[i],使用兩張貨幣arr[i]…使用k張貨幣arr[i](k=aim//arr[i]),對應到dp表裡分別是[i-1][j]、dp[i-1][j-arr[i]]、dp[i-1][j-2*arr[i]]…dp[i-1][j-k*arr[i]]。
時間複雜度為o(n*aim^2),空間複雜度o(n*aim)。
#動態規劃方法
defcoins3
(arr, aim):
if arr == none
or len(arr) == 0
or aim < 0:
return
0 row = len(arr)
dp = [[0
for i in range(aim+1)]for j in range(row)]
for i in range(row):
dp[i][0] = 1
for j in range(1, aim//arr[0]+1):
dp[0][arr[0]*j] = 1
for i in range(1, row):
for j in range(1, aim+1):
num = 0
for k in range(j//arr[i]+1):
num += dp[i-1][j-arr[i]*k]
dp[i][j] = num
return dp[row-1][aim]
對動態規劃進行優化。上述動態規劃中計算dp[i][j]的過程比較繁瑣,其實計算dp的第三步可以進行優化。
在上述動態規劃的第三步中,dp[i][j] = dp[i-1][j] + dp[i-1][j-arr[i]] + dp[i-1][j-2*arr[i]] + …dp[i-1][j-k*arr[i]]。我們發現,這個等式中除了第一項以外其他項的累加和其實就是dp[i][j-arr[i]],只不過是將j-arr[i]這個整體當作j,所以步驟3可以優化為dp[i][j] = dp[i-1][j] + dp[i][j-arr[i]]。
時間複雜度為o(n*aim),空間複雜度o(n*aim)。
#動態規劃公升級版
defcoins4
(arr, aim):
if arr == none
or len(arr) == 0
or aim < 0:
return
0 row = len(arr)
dp = [[0
for i in range(aim+1)] for j in range(row)]
for i in range(row):
dp[i][0] = 1
for j in range(1, aim//arr[0]+1):
dp[0][arr[0]*j] = 1
for i in range(1,row):
for j in range(1, aim+1):
dp[i][j] = dp[i-1][j]
dp[i][j] += dp[i][j-arr[i]] if j-arr[i] >= 0
else
0return dp[row-1][aim]
對動態規劃進行再優化。即使用空間壓縮的方法,優化動態規劃的空間複雜度,只是用一維陣列記錄資料而不是矩陣。
時間複雜度為o(n*aim),空間複雜度o(aim)。
#動態規劃公升級版+空間壓縮
defcoins5
(arr, aim):
if arr == none
or len(arr) == 0
or aim < 0:
return
0 dp = [0
for i in range(aim+1)]
for i in range(aim//arr[0]+1):
dp[arr[0]*i] = 1
for i in range(len(arr)):
for j in range(1, aim+1):
dp[j] += dp[j-arr[i]] if j-arr[i] >= 0
else
0return dp[aim]
動態規劃 換錢的方法數
演算法專題導航頁面 題目描述 給定陣列arr,設陣列長度為n,arr中所有的值都為正整數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定乙個整數aim,代表要找的錢數,求換錢的方法數有多少種。由於方法的種數比較大,所以要求輸出對10 9 7進行取模後的答案。輸入描述 輸入包括...
換錢的方法數(動態規劃講的很好)
給定陣列arr,arr中所有的值都為正數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定乙個整數aim代表要找的錢數,求換錢有多少種方法。舉例 arr 5,10,25,1 aim 0。組成0元的方法有1種,就是所有面值的貨幣都不用。所以返回1。arr 5,10,25,1 ai...
動態規劃 換錢方法
換錢方法 給定陣列arr,arr中所有的值都是整數且不重複,每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定乙個整數,aim代表要找的錢數,求換錢有多少種方法。思路 1.建立一張二維表dp,m n 其中m表示貨幣的種類,行n表示 target 1 即是目標錢數 1 即是 下表以目標錢數...