經(jīng)典動態(tài)規(guī)劃:完全背包問題

讀完本文垄潮,你可以去力扣拿下如下題目:

518.零錢兌換II

-----------

零錢兌換 2 是另一種典型背包問題的變體,我們前文已經(jīng)講了 經(jīng)典動態(tài)規(guī)劃:0-1 背包問題从橘。

希望你已經(jīng)看過前兩篇文章,看過了動態(tài)規(guī)劃和背包問題的套路,這篇繼續(xù)按照背包問題的套路,列舉一個背包問題的變形狈蚤。

本文聊的是 LeetCode 第 518 題 Coin Change 2,題目如下:

title
int change(int amount, int[] coins);

PS:至于 Coin Change 1划纽,在我們前文 動態(tài)規(guī)劃套路詳解 寫過脆侮。

我們可以把這個問題轉(zhuǎn)化為背包問題的描述形式

有一個背包,最大容量為 amount勇劣,有一系列物品 coins靖避,每個物品的重量為 coins[i]潭枣,每個物品的數(shù)量無限。請問有多少種方法幻捏,能夠把背包恰好裝滿盆犁?

這個問題和我們前面講過的兩個背包問題,有一個最大的區(qū)別就是篡九,每個物品的數(shù)量是無限的谐岁,這也就是傳說中的「完全背包問題」,沒啥高大上的榛臼,無非就是狀態(tài)轉(zhuǎn)移方程有一點變化而已伊佃。

下面就以背包問題的描述形式,繼續(xù)按照流程來分析沛善。

PS:我認真寫了 100 多篇原創(chuàng)航揉,手把手刷 200 道力扣題目,全部發(fā)布在 labuladong的算法小抄金刁,持續(xù)更新帅涂。建議收藏,按照我的文章順序刷題尤蛮,掌握各種算法套路后投再入題海就如魚得水了媳友。

解題思路

第一步要明確兩點,「狀態(tài)」和「選擇」产捞。

狀態(tài)有兩個庆锦,就是「背包的容量」和「可選擇的物品」,選擇就是「裝進背包」或者「不裝進背包」嘛轧葛,背包問題的套路都是這樣。

明白了狀態(tài)和選擇艇搀,動態(tài)規(guī)劃問題基本上就解決了尿扯,只要往這個框架套就完事兒了:

for 狀態(tài)1 in 狀態(tài)1的所有取值:
    for 狀態(tài)2 in 狀態(tài)2的所有取值:
        for ...
            dp[狀態(tài)1][狀態(tài)2][...] = 計算(選擇1,選擇2...)

第二步要明確 dp 數(shù)組的定義焰雕。

首先看看剛才找到的「狀態(tài)」衷笋,有兩個,也就是說我們需要一個二維 dp 數(shù)組矩屁。

dp[i][j] 的定義如下:

若只使用前 i 個物品辟宗,當(dāng)背包容量為 j 時,有 dp[i][j] 種方法可以裝滿背包吝秕。

換句話說泊脐,翻譯回我們題目的意思就是:

若只使用 coins 中的前 i 個硬幣的面值,若想湊出金額 j烁峭,有 dp[i][j] 種湊法容客。

經(jīng)過以上的定義秕铛,可以得到:

base case 為 dp[0][..] = 0, dp[..][0] = 1缩挑。因為如果不使用任何硬幣面值但两,就無法湊出任何金額;如果湊出的目標(biāo)金額為 0供置,那么“無為而治”就是唯一的一種湊法谨湘。

我們最終想得到的答案就是 dp[N][amount],其中 Ncoins 數(shù)組的大小芥丧。

大致的偽碼思路如下:

int dp[N+1][amount+1]
dp[0][..] = 0
dp[..][0] = 1

for i in [1..N]:
    for j in [1..amount]:
        把物品 i 裝進背包,
        不把物品 i 裝進背包
return dp[N][amount]

第三步紧阔,根據(jù)「選擇」,思考狀態(tài)轉(zhuǎn)移的邏輯娄柳。

注意寓辱,我們這個問題的特殊點在于物品的數(shù)量是無限的,所以這里和之前寫的背包問題文章有所不同赤拒。

如果你不把這第 i 個物品裝入背包秫筏,也就是說你不使用 coins[i] 這個面值的硬幣,那么湊出面額 j 的方法數(shù) dp[i][j] 應(yīng)該等于 dp[i-1][j]挎挖,繼承之前的結(jié)果这敬。

如果你把這第 i 個物品裝入了背包,也就是說你使用 coins[i] 這個面值的硬幣蕉朵,那么 dp[i][j] 應(yīng)該等于 dp[i][j-coins[i-1]]崔涂。

首先由于 i 是從 1 開始的,所以 coins 的索引是 i-1 時表示第 i 個硬幣的面值始衅。

dp[i][j-coins[i-1]] 也不難理解冷蚂,如果你決定使用這個面值的硬幣,那么就應(yīng)該關(guān)注如何湊出金額 j - coins[i-1]汛闸。

比如說蝙茶,你想用面值為 2 的硬幣湊出金額 5,那么如果你知道了湊出金額 3 的方法诸老,再加上一枚面額為 2 的硬幣隆夯,不就可以湊出 5 了嘛。

綜上就是兩種選擇别伏,而我們想求的 dp[i][j] 是「共有多少種湊法」蹄衷,所以 dp[i][j] 的值應(yīng)該是以上兩種選擇的結(jié)果之和

for (int i = 1; i <= n; i++) {
    for (int j = 1; j <= amount; j++) {
        if (j - coins[i-1] >= 0)
            dp[i][j] = dp[i - 1][j] 
                     + dp[i][j-coins[i-1]];
return dp[N][W]

最后一步,把偽碼翻譯成代碼厘肮,處理一些邊界情況愧口。

我用 Java 寫的代碼,把上面的思路完全翻譯了一遍轴脐,并且處理了一些邊界問題:

int change(int amount, int[] coins) {
    int n = coins.length;
    int[][] dp = amount int[n + 1][amount + 1];
    // base case
    for (int i = 0; i <= n; i++) 
        dp[i][0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= amount; j++)
            if (j - coins[i-1] >= 0)
                dp[i][j] = dp[i - 1][j] 
                         + dp[i][j - coins[i-1]];
            else 
                dp[i][j] = dp[i - 1][j];
    }
    return dp[n][amount];
}

而且调卑,我們通過觀察可以發(fā)現(xiàn)抡砂,dp 數(shù)組的轉(zhuǎn)移只和 dp[i][..]dp[i-1][..] 有關(guān),所以可以壓縮狀態(tài)恬涧,進一步降低算法的空間復(fù)雜度:

int change(int amount, int[] coins) {
    int n = coins.length;
    int[] dp = new int[amount + 1];
    dp[0] = 1; // base case
    for (int i = 0; i < n; i++)
        for (int j = 1; j <= amount; j++)
            if (j - coins[i] >= 0)
                dp[j] = dp[j] + dp[j-coins[i]];
    
    return dp[amount];
}

這個解法和之前的思路完全相同注益,將二維 dp 數(shù)組壓縮為一維,時間復(fù)雜度 O(N*amount)溯捆,空間復(fù)雜度 O(amount)丑搔。

至此,這道零錢兌換問題也通過背包問題的框架解決了提揍。

_____________

我的 在線電子書 有 100 篇原創(chuàng)文章啤月,手把手帶刷 200 道力扣題目,建議收藏劳跃!對應(yīng)的 GitHub 算法倉庫 已經(jīng)獲得了 70k star谎仲,歡迎標(biāo)星!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末刨仑,一起剝皮案震驚了整個濱河市郑诺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杉武,老刑警劉巖辙诞,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異轻抱,居然都是意外死亡飞涂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門祈搜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來较店,“玉大人,你說我怎么就攤上這事容燕≡笪鳎” “怎么了?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵缰趋,是天一觀的道長。 經(jīng)常有香客問我陕见,道長秘血,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任评甜,我火速辦了婚禮灰粮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忍坷。我一直安慰自己粘舟,他們只是感情好熔脂,可當(dāng)我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著柑肴,像睡著了一般霞揉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上晰骑,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天适秩,我揣著相機與錄音,去河邊找鬼硕舆。 笑死秽荞,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的抚官。 我是一名探鬼主播扬跋,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼凌节!你這毒婦竟也來了钦听?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤刊咳,失蹤者是張志新(化名)和其女友劉穎彪见,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體娱挨,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡余指,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了跷坝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片酵镜。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖柴钻,靈堂內(nèi)的尸體忽然破棺而出淮韭,到底是詐尸還是另有隱情,我是刑警寧澤贴届,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布靠粪,位于F島的核電站,受9級特大地震影響毫蚓,放射性物質(zhì)發(fā)生泄漏占键。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一元潘、第九天 我趴在偏房一處隱蔽的房頂上張望畔乙。 院中可真熱鬧,春花似錦翩概、人聲如沸牲距。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牍鞠。三九已至咖摹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間皮服,已是汗流浹背楞艾。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留龄广,地道東北人硫眯。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像择同,于是被迫代替她去往敵國和親两入。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,490評論 2 348