動態規劃系列(2) 找零錢問題

2021-07-30 17:17:26 字數 4530 閱讀 5394

refer:

tom在自動售貨機上買了一瓶飲料,售價37美分,他投入了1美元(1美元 = 100美分),現在自動售貨機需要找錢給他。售貨機中現在只有四種面額的硬幣:1美分、5美分、10美分、25美分,每種硬幣的數量充足。現在要求使用最少數量的硬幣,給tom找錢,求出這個最少數量是多少。

自動售賣機需要給tom找零錢63美分,而售賣機中只有四種面額的硬幣可以使用,現在的核心問題就是如何用四種面額的硬幣來湊夠63美分,並且使用的硬幣數量最少。

現在我們換個角度來思考這個問題:

是不是可以將問題規模先縮小?比如我不知道湊夠63美分最少需要多少個硬幣,那湊夠1美分、2美分的方案則顯而易見是可以馬上知道的。

為了後面敘述方便,用f(i) = n這個等式來表示這樣一種含義:湊夠i美分(0 <= i <= 63)所需要的最少硬幣數量為n個,那麼我們從湊夠0美分開始寫:

從上面的分析過程可以看出,要湊夠i美分,就要考慮如下各種方案的最小值:

1 + f(i - value[j]),其中value[j]表示第j種(j從0開始,0 <= j < 4)面值且value[j] <= i

那麼現在就可以寫出狀態轉移方程了:

f(i) = 0, i = 0

f(i) = 1, i = 1

f(i) = min, i > 1,value[j] <= i

# coding:utf-8

# 找零錢問題演算法實現:基本版

# 4種硬幣面值

values = [1,5,10,25]

# 湊夠amount這麼多錢數需要的最少硬幣個數

defmincoins

(amount):

# 需要的最少硬幣個數

ret_min = amount

if amount < 1:

ret_min = 0

# 如果要找的錢數恰好是某種硬幣的面值,那麼最少只需乙個硬幣

elif amount in values:

ret_min = 1

else:

# 遍歷面值陣列中面值小於等於amount的那些元素

for v in [x for x in values if x <= amount]:

# 用面值為v的硬幣+其他硬幣找零所需的最少硬幣數

min_num = 1 + mincoins(amount - v)

# 判斷min_num和ret_min的大小,更新ret_min

if min_num < ret_min:

ret_min = min_num

return ret_min

defmain

():print mincoins(63)

main()

將上面指令碼儲存成coins.py檔案,在ipython中執行:%time %run coins.py,得到的結果如下:

cpu times: user 1min 45s, sys: 0 ns, total: 1min 45s

wall time: 1min 45s

分析:可以看出,在我的電腦上,僅僅是為了計算用4種面額找63美分零錢,就耗時1分鐘45秒(105秒),這是無法忍受的。那麼究竟為什麼耗時這麼巨大?下面對**稍加改造進行一下效能分析。

# coding:utf-8

# 找零錢問題演算法實現:基本版效能分析

# 統計遞迴次數

recursion_num = 0

# 4種硬幣面值

values = [1,5,10,25]

# 湊夠amount這麼多錢數需要的最少硬幣個數

defmincoins

(amount):

global recursion_num

# 需要的最少硬幣個數

ret_min = amount

if amount < 1:

ret_min = 0

# 如果要找的錢數恰好是某種硬幣的面值,那麼最少只需乙個硬幣

elif amount in values:

ret_min = 1

else:

# 遍歷面值陣列中面值小於等於amount的那些元素

for v in [x for x in values if x <= amount]:

# 用面值為v的硬幣+其他硬幣找零所需的最少硬幣數

min_num = 1 + mincoins(amount - v)

# 判斷min_num和ret_min的大小,更新ret_min

if min_num < ret_min:

ret_min = min_num

recursion_num += 1

return ret_min

defmain

():print mincoins(63)

print recursion_num

main()

將上面指令碼儲存成coins.py檔案,在ipython中執行:%time %run coins.py,得到的結果如下:

cpu times: user 2min, sys: 36 ms, total: 2min

wall time: 2min

分析:可見,mincoins函式一共被遞迴呼叫了67716925次,真是難以想象,為了計算最多64個函式值(amount取0~63),居然遞迴呼叫了函式mincoins 67716925次,平均求每個值呼叫了1058076次。那麼問題出在**了呢?出在了重複計算上,有很多值被重複計算了上百萬次。那麼如何儘量減少重複計算呢?下面用乙個快取陣列來快取每次求出的函式值,供後面使用,從而減少重複計算。

# coding:utf-8

# 找零錢問題演算法實現:基本版效能分析

# 統計遞迴次數

recursion_num = 0

# 4種硬幣面值

values = [1,5,10,25]

# 快取陣列,為乙個一維陣列,用於快取每次遞迴函式求得的值

# cache[i]表示湊夠i美分所需的最少硬幣個數,cache的元素都被初始化為-1,表示個數未知

cache =

# 初始化快取陣列

definit

(amount):

global cache

cache = [-1] * (amount + 1)

# 湊夠amount這麼多錢數需要的最少硬幣個數

defmincoins

(amount):

global recursion_num

global cache

# 需要的最少硬幣個數

ret_min = amount

# 如果快取陣列中有對應的值,那麼直接從中取,不再重複計算了

if cache[amount] != -1:

ret_min = cache[amount]

elif amount < 1:

ret_min = 0

# 如果要找的錢數恰好是某種硬幣的面值,那麼最少只需乙個硬幣

elif amount in values:

ret_min = 1

else:

# 遍歷面值陣列中面值小於等於amount的那些元素

for v in [x for x in values if x <= amount]:

# 用面值為v的硬幣+其他硬幣找零所需的最少硬幣數

min_num = 1 + mincoins(amount - v)

# 判斷min_num和ret_min的大小,更新ret_min

if min_num < ret_min:

ret_min = min_num

# 更新快取陣列

cache[amount] = ret_min

recursion_num += 1

return ret_min

defmain

(): init(63)

print mincoins(63)

print cache

print recursion_num

main()

將上面指令碼儲存成coins.py檔案,在ipython中執行:%time %run coins.py,得到的結果如下:

[-1, 1, 2, 3, 4, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 2, 3, 4, 5, 6, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 3, 4, 5, 6, 7, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 3, 4, 5, 6]

cpu times: user 4 ms, sys: 0 ns, total: 4 ms

wall time: 2.2 ms

分析:可見,cache陣列除了cache[0]沒被用到以外,其他元素都被利用到了,利用率還是很高的。使用快取陣列後,mincoins函式的遞迴呼叫次數從67716925次降低到了206次,降低了328722倍;程式耗時從105秒降低到了2.2ms,降低了47727倍,優化效果是巨大的。

動態規劃5 找零錢問題

題目 有陣列penny,penny中所有的值都為正數且不重複。每個值代表一種面值的貨幣,每種面值的貨幣可以使用任意張,再給定乙個整數aim 小於等於1000 代表要找的錢數,求換錢有多少種方法。給定陣列penny及它的大小 小於等於50 同時給定乙個整數aim,請返回有多少種方法可以湊成aim。樣例...

動態規劃 找零錢問題 收藏

view plaincopy to clipboardprint?include using namespace std const int m 1000 const int n 3 int coint n int count m 1 count i 表示湊合數量為i所需最少的錢幣數量,則count...

找零錢問題 動態規劃 python

問題描述 設有n種不同面值的硬幣,各硬幣的面值存於陣列t 1 n 中。現要用這些面值的硬幣來找錢,可以實用的各種面值的硬幣個數不限。當只用硬幣面值t 1 t 2 t i 時,可找出錢數j的最少硬幣個數記為c i,j 若只用這些硬幣面值,找不出錢數j時,記c i,j 程式設計任務 設計乙個動態規劃演算...