合縱連橫: 找零錢的DP框架_理解背後的本質

小松鼠-avatar-img
發佈於演算法題目解析 個房間
更新於 發佈於 閱讀時間約 13 分鐘

最近會試著寫一些統整類的文章,
幫助讀者、觀眾整理、吸收、複習已經學習到的演算法框架。


找零錢框架

在以前學過的題目中,我們已經學會了找零錢的抽象思考邏輯與框架,就是試著用每一種銅板去湊出n元(也就是找零錢的過程)


寫成虛擬碼或演算法,找零錢用了幾枚銅板可以這樣表達

# 銅板數目累加​
# n-coin 元 + coin這枚銅板,就可以湊出n元。
dp[ n ] = dp[ n- coin ] + 1


寫成虛擬碼或演算法,找零錢的方法數可以這樣表達

for coin in coins:
# 方法數累加​
# n-coin 元 + coin這枚銅板,就可以湊出n元。
dp[ n ] += dp[ n - coin ]


接下來,我們會用這個上面這兩種結構,貫穿一些同類型,有關聯的題目
(請讀者、或觀眾留意上面的這種DP結構,之後的解說會反覆出現)

最精簡找零 Coin Change I (原本這題的專門教學文章在這裡)

對於 Coin Change I 來說,我們要找的是最精簡的找零數目,也就是
用最少的銅板湊出n元,於是我們就用DP找零錢用了幾枚銅板的演算法來解。
就像下面看到的這樣:

class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:

# key: $value
# value: minimal coin change of $value
dp = defaultdict(lambda: math.inf)

# Base case
# $0 is reached by taking nothing
dp[0] = 0

# General case:
# Try to make change with current coin
# 請注意看,剛剛介紹的DP演算法框架就出現在這裡!
for coin in coins:
for value in range(coin, amount+1):
dp[value] = min(dp[value], dp[value-coin]+1 )


if dp[amount] == math.inf:
return -1

return dp[amount]

有沒有別的題目用到同樣的結構?

其實有喔,在Perfect Squares 這題就是。


最精簡找零 的衍伸變形 Perfect Squares



這題要求我們計算整數n的最精簡的完全平方數的化簡次數。

我們可以把n視為目標金額(要找開的對象),所有小於等於n的完全平方數視為題目供應的銅板陣列

如此一來,這題就被我們化簡到最精簡找零

於是我們就用DP找零錢用了幾枚銅板的演算法來解。就像下面看到的這樣:

class Solution:
def numSquares(self, n: int) -> int:

INF = sys.maxsize
dp = [ INF for _ in range(n+1) ]
dp[0] = 0

root = 1
square = root*root

# for each square number 1, 4, 9, 16, 25...
# 請注意看,剛剛介紹的DP演算法框架就出現在這裡!
while( square <= n ) :

# update dp value for number from square to n
for i in range(square, n+1) :

dp[i] = min( dp[i], dp[i-square]+1 )

# go to next square number
root += 1
square = root*root


return dp[n]

Perfect Squares 和上面那一題 Coin Change I 九成都很像!對吧~

只是改成特殊的銅板(都是完全平方數)而已,其他思考邏輯都相同



找零錢的方法總數(嚴格一點說,就是數學上的組合數) Coin Change II
(原本這題的專門教學文章在這裡)

對於 Coin Change II 來說,我們要找的是找零錢的方法總數,就是數學上的組合數(與次序無關)。

例如 $5 + $1 和 $1 + $5 會被視為一種而已(因為組合數不考慮順序)
這是實作上的細節,請特別留意

想清楚題目要問的
究竟是 組合數(不考慮順序),還是排列數(考慮順序),這兩者是有區別的。


於是我們就用DP找零錢的方法數的演算法來解。就像下面看到的這樣:

我們每次迭代時,外圈先固定用某一種銅板針對所有金額去找零錢確保同一種銅板不會再之後的找零錢過程中重複出現

class Solution:
def change(self, amount: int, coins: List[int]) -> int:

# base case:
# amount 0's method count = 1 (by taking no coins)
dp = [1] + [ 0 for _ in range(amount)]

# make change with current coin, from small coin to large coin
# 請注意看,剛剛介紹的DP演算法框架就出現在這裡!
for cur_coin in coins:

# update change method count from small amount to large amount
for small_amount in range(cur_coin, amount+1):

# current small amount can make changed with current coin
dp[small_amount] += dp[small_amount - cur_coin]

return dp[amount]

好,現在我們知道找零錢的組合數了。

那...假如別的題目要求我們列出找零錢的具體方法呢?


其實有,只是被出題者包裝過,用別的樣貌呈現,但本質還是相同的,

這題就是Combination Sum


找零錢的具體方法 Combination Sum


原本Coin Change II是計算有幾個組合數,
現在Combination Sum是改成紀錄組合的狀態,用同樣的手法更新即可


於是我們就用DP找零錢的方法數的演算法來紀錄組合的狀態。就像下面看到的這樣:

我們每次迭代時,外圈先固定用某一種銅板針對所以金額去找零錢確保同一種銅板不會再之後的找零錢過程中重複出現

class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:

dp = defaultdict(list)

## Base case:
# $0 is reached by taking nothing
dp[0] = [ [ ] ]

coins = candidates

## General cases:
# 請注意看,剛剛介紹的DP演算法框架就出現在這裡!
for coin in coins:
for amount in range(coin, target+1):

# satisfy $amount from ($amount-$coin) + $coin
for prev_coin_change in dp[ amount - coin ]:
dp[amount] += [ prev_coin_change + [coin] ]

return dp[ target ]

Combination Sum 和上面那一題 Coin Change II 九成都很像!對吧~

只是改成紀錄組合的狀態而已,其他思考邏輯都相同


那有沒有類似找零錢的題目,但是要考慮順序的呢?也就是要求我們計算排列數的?

其實有,只是被出題者包裝過,用別的樣貌呈現,但本質還是相同的,
這題就是Combination Sum IV

找零錢的方法排列數(嚴格說,就是數學上的排列數) Combination Sum IV



對於 Combination Sum IV 來說,
我們可以把target是為目標金額(要找開的對象),nums視為題目供應的銅板陣列
如此一來,這題就被我們化簡到找零錢問題。

由從題意和範例可以知道,這題把不同的順序視為不同的兩種。

例如 $5 + $1 和 $1 + $5 會被視為不同的兩種而已(因為排列數要考慮順序)
這是實作上的細節,請特別留意

想清楚題目要問的
究竟是 組合數(不考慮順序),還是排列數(考慮順序),這兩者是有區別的。


於是我們就用DP找零錢的方法數的演算法來解。就像下面看到的這樣:

我們每次迭代時,外圈反而先迭代所有金額內圈才迭代每一種銅板針對當下的金額去找零錢,允許同一種銅板在之後的找零錢過程中又重複出現

class Solution:
def combinationSum4(self, nums: List[int], target: int) -> int:

## Dictionary
# key: amount
# value: method count to make change up to specific amount
dp = defaultdict(int)

# $0 is reached by taking nothing, only one way to do so
dp[0] = 1

# For each value in range [1, target]
# 請注意看,剛剛介紹的DP演算法框架就出現在這裡!
for value in range(1, target+1):

# Try each possible coin
for coin in nums:

# Coin is too big, impossible to make change
if coin > value: continue

# Make change with smaller coin
# $value = $(value-coin) + $coin
dp[value] += dp[value-coin]

# Total method count to make change up to $target
return dp[target]


Combination Sum IV 和上面那一題 Coin Change II 九成都很像!對吧~

只是改成紀錄排列數(考慮順序)而已,其他思考邏輯都相同


細微的差異就是我們用銅板放在內層迴圈還是外層迴圈
來控制同一種銅板在之後可不可以重複出現


結語

好,今天一口氣介紹了最精華的部分,

通用的Coin Change 找零錢模型的框架,還有相關的衍伸變化題與演算法化簡流程,

希望能幫助讀者、觀眾徹底理解它的原理,
並且能夠舉一反三,洞察其背後的本質和了解相關的應用領域!


感謝收看囉! 我們下篇文章再見~

留言
avatar-img
留言分享你的想法!
貓派主播-avatar-img
2024/03/18
我又看了什麼天書哈哈哈哈
小松鼠-avatar-img
發文者
2024/03/18
貓派主播 哈哈 沒關係 喝一杯香香的咖啡,看到美美的鑽石,懷中有可愛貓咪踏踏就夠了 >////<
小松鼠-avatar-img
發文者
2024/09/09
DP演算法框架 與 推薦的DP學習路徑 (持續更新中)提及了這篇文章,趕快過去看看吧!
小松鼠-avatar-img
發文者
2024/06/10
avatar-img
小松鼠的演算法樂園
95會員
427內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
2024/09/13
給定一個整數陣列arr,和一串區間XOR請求queries。 請計算queries所請求的區間XOR值,並且以陣列的形式返回答案。
Thumbnail
2024/09/13
給定一個整數陣列arr,和一串區間XOR請求queries。 請計算queries所請求的區間XOR值,並且以陣列的形式返回答案。
Thumbnail
2024/08/27
Path with Maximum Probability 題目給定一個無向圖(雙向移動皆可), 提供每條邊的起終點,和每條邊對應的通過時的成功機率。 請問從起點start走到終點end的最高成功機率是多少? 如果完全沒有路徑可以抵達,則返回0。
Thumbnail
2024/08/27
Path with Maximum Probability 題目給定一個無向圖(雙向移動皆可), 提供每條邊的起終點,和每條邊對應的通過時的成功機率。 請問從起點start走到終點end的最高成功機率是多少? 如果完全沒有路徑可以抵達,則返回0。
Thumbnail
2024/08/21
題目敘述 664. Strange Printer 有一台奇怪的印表機, 每次操作只能連續印同樣的字母,但是列印的長度可以自由控制。 而且,印刷的時候,可以蓋過去舊的字元。 (這邊當然不合常理,讀者可以理解成塗了立可帶再蓋過去的情境) 給定一個輸入字串s,請問最少需要幾次操作,才能印出字串s?
Thumbnail
2024/08/21
題目敘述 664. Strange Printer 有一台奇怪的印表機, 每次操作只能連續印同樣的字母,但是列印的長度可以自由控制。 而且,印刷的時候,可以蓋過去舊的字元。 (這邊當然不合常理,讀者可以理解成塗了立可帶再蓋過去的情境) 給定一個輸入字串s,請問最少需要幾次操作,才能印出字串s?
Thumbnail
看更多
你可能也想看
Thumbnail
大家好,我是一名眼科醫師,也是一位孩子的媽 身為眼科醫師的我,我知道視力發展對孩子來說有多關鍵。 每到開學季時,診間便充斥著許多憂心忡忡的家屬。近年來看診中,兒童提早近視、眼睛疲勞的案例明顯增加,除了3C使用過度,最常被忽略的,就是照明品質。 然而作為一位媽媽,孩子能在安全、舒適的環境
Thumbnail
大家好,我是一名眼科醫師,也是一位孩子的媽 身為眼科醫師的我,我知道視力發展對孩子來說有多關鍵。 每到開學季時,診間便充斥著許多憂心忡忡的家屬。近年來看診中,兒童提早近視、眼睛疲勞的案例明顯增加,除了3C使用過度,最常被忽略的,就是照明品質。 然而作為一位媽媽,孩子能在安全、舒適的環境
Thumbnail
我的「媽」呀! 母親節即將到來,vocus 邀請你寫下屬於你的「媽」故事——不管是紀錄爆笑的日常,或是一直想對她表達的感謝,又或者,是你這輩子最想聽她說出的一句話。 也歡迎你曬出合照,分享照片背後的點點滴滴 ♥️ 透過創作,將這份情感表達出來吧!🥹
Thumbnail
我的「媽」呀! 母親節即將到來,vocus 邀請你寫下屬於你的「媽」故事——不管是紀錄爆笑的日常,或是一直想對她表達的感謝,又或者,是你這輩子最想聽她說出的一句話。 也歡迎你曬出合照,分享照片背後的點點滴滴 ♥️ 透過創作,將這份情感表達出來吧!🥹
Thumbnail
題目敘述 Combination Sum IV 給定一個輸入陣列nums,和目標值target,從nums裡面挑數字去湊出總和 = target,數字可以重複挑選。 請問有多少排列數可以湊出target? 註: 排列數的意思就是位置不同代表兩種不同的方法數。
Thumbnail
題目敘述 Combination Sum IV 給定一個輸入陣列nums,和目標值target,從nums裡面挑數字去湊出總和 = target,數字可以重複挑選。 請問有多少排列數可以湊出target? 註: 排列數的意思就是位置不同代表兩種不同的方法數。
Thumbnail
題目給定一個布林代數的二元樹,要求我們計算最後的結果。 葉子節點都是真假值 非葉子節點都是布林運算子
Thumbnail
題目給定一個布林代數的二元樹,要求我們計算最後的結果。 葉子節點都是真假值 非葉子節點都是布林運算子
Thumbnail
這篇文章,會帶著大家複習以前學過的 格子點DP框架, 並且以最小成本的下降路徑的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 最小成本下降路徑的形式 每個格子點的值代表經過的成本。 要求從最上面那排往下方走,落到最下一排的最小成本的下降路徑。
Thumbnail
這篇文章,會帶著大家複習以前學過的 格子點DP框架, 並且以最小成本的下降路徑的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 最小成本下降路徑的形式 每個格子點的值代表經過的成本。 要求從最上面那排往下方走,落到最下一排的最小成本的下降路徑。
Thumbnail
這篇文章,會帶著大家複習以前學過的數列DP框架, 並且以費式數列、爬樓梯、骨牌拚接的應用與遞迴數列概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 數列DP與遞迴數列常見的形式 如果是遞迴數列,常常看到以函數型式表達
Thumbnail
這篇文章,會帶著大家複習以前學過的數列DP框架, 並且以費式數列、爬樓梯、骨牌拚接的應用與遞迴數列概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 數列DP與遞迴數列常見的形式 如果是遞迴數列,常常看到以函數型式表達
Thumbnail
這篇文章,會帶著大家複習以前學過的二進位DP框架, 並且以0~N的整數有幾個bit1,有幾個bit0的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 常見的考法 請問整數k有幾個bit1? 有幾個bit0? 請問整數0到整數N分別各有幾個bit1? 有幾個
Thumbnail
這篇文章,會帶著大家複習以前學過的二進位DP框架, 並且以0~N的整數有幾個bit1,有幾個bit0的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 常見的考法 請問整數k有幾個bit1? 有幾個bit0? 請問整數0到整數N分別各有幾個bit1? 有幾個
Thumbnail
這篇文章,會帶著大家複習以前學過的 區間DP框架, 並且以回文子字串、回文子序列的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 回文字串的基本定義 s = s[::-1] 也就是說字串s的正序 和 逆序完全相同。 回文字串的基本結構 空字串"
Thumbnail
這篇文章,會帶著大家複習以前學過的 區間DP框架, 並且以回文子字串、回文子序列的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 回文字串的基本定義 s = s[::-1] 也就是說字串s的正序 和 逆序完全相同。 回文字串的基本結構 空字串"
Thumbnail
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
Thumbnail
這篇文章,會帶著大家複習以前學過的遞回框架, 並且鏈結串列的概念與應用為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 遞回框架 尋找共通模式(common pattern),對應到演算法的General case 確立初始條件(initial conditio
Thumbnail
這篇文章,會帶著大家複習以前學過的區間DP框架, 並且以區間DP的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 基本的區間DP框架(限制條件: 相鄰的兩項不允許同時選擇) 在House Robbery這題中,我們學會了一種基本的區間DP框架。
Thumbnail
這篇文章,會帶著大家複習以前學過的區間DP框架, 並且以區間DP的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 基本的區間DP框架(限制條件: 相鄰的兩項不允許同時選擇) 在House Robbery這題中,我們學會了一種基本的區間DP框架。
Thumbnail
這篇文章,會帶著大家複習以前學過的二分搜尋法(Binary Search)框架, 並且以二分搜尋法的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 Binary search 二分搜尋法框架 用途: 在已經排序好的數列中尋找目標值。
Thumbnail
這篇文章,會帶著大家複習以前學過的二分搜尋法(Binary Search)框架, 並且以二分搜尋法的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個實用的演算法框架。 Binary search 二分搜尋法框架 用途: 在已經排序好的數列中尋找目標值。
Thumbnail
最近會試著寫一些統整類的文章, 幫助讀者、觀眾整理、吸收、複習已經學習到的演算法框架。 找零錢框架 在以前學過的題目中,我們已經學會了考零錢的抽象思考邏輯與框架,就是試著用每一種銅板去湊出n元(也就是找零錢的過程) 寫成虛擬碼或演算法,找零錢用了幾枚銅板可以這樣表達 # 銅板數目累加​
Thumbnail
最近會試著寫一些統整類的文章, 幫助讀者、觀眾整理、吸收、複習已經學習到的演算法框架。 找零錢框架 在以前學過的題目中,我們已經學會了考零錢的抽象思考邏輯與框架,就是試著用每一種銅板去湊出n元(也就是找零錢的過程) 寫成虛擬碼或演算法,找零錢用了幾枚銅板可以這樣表達 # 銅板數目累加​
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News