從【零錢兌換】問題看01揹包和完全揹包問題

peterzh6發表於2024-04-26

https://leetcode.cn/problems/coin-change/description/?envType=study-plan-v2&envId=top-interview-150

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        dp = [float('inf')] * (amount + 1)
        dp[0] = 0

        for i in range(1, amount + 1):
            for coin in coins:
                if coin <= i:
                    dp[i] = min(dp[i], dp[i - coin] + 1)

        return dp[amount] if dp[amount] != float('inf') else -1

完全揹包問題裸題

轉載自OI wiki
01揹包問題狀態轉移方程

文章還提到了錯誤的演算法,為什麼會錯誤呢,看一下情況:
如果使用我之前提到的錯誤演算法(從小到大更新揹包容量)處理同一個例子,我們會看到不同的結果。錯誤的方法如下:

for i in range(1, n + 1):  # 遍歷物品
    for l in range(0, W - w[i] + 1):  # 從小到大遍歷揹包容量
        f[l + w[i]] = max(f[l] + v[i], f[l + w[i]])

還是使用之前的物品和揹包情況:

  • 物品1:重量3,價值30
  • 物品2:重量4,價值50
  • 揹包的總容量是7

來看看使用錯誤方法時會發生什麼:

  1. 初始化f[0...7]為0。
  2. 處理物品1:
    • 從容量0開始,只有容量3及以上的f可以被更新。
    • 更新後,f[3]=30, f[4]=30, f[5]=30, f[6]=30, f[7]=30
  3. 處理物品2:
    • 從容量0開始,對於每個lf[l + 4]可以被更新。
    • 因為從小到大的更新方式,f[4]先被更新為50,然後在處理到l=4時,f[8]會被嘗試更新(儘管f[8]超出了揹包的容量),而f[4]作為之前更新過的值,能在此基礎上再加上物品2的價值,導致物品2被錯誤地考慮了兩次。

最終,f陣列在容量4的位置上可能反映的是物品2被放入兩次的情況(實際上因為陣列索引不會到8,但邏輯上這種重複使用已經發生),如果揹包容量足夠大,你會看到類似的錯誤累加。

這個錯誤方法雖然在本例中因為揹包容量限制沒有顯現出非法值,但在更復雜或容量更大的情況下,會導致演算法錯誤地計算出某些物品可以被多次加入揹包,從而得到錯誤的最大價值。這種情況下,演算法的輸出不符合01揹包的定義,即每個物品至多被放入一次。

相關文章