0 - 1 背包問題

題目

給定一組物品扩灯,每種物品都有自己的重量和價格媚赖,在限定的總重量內(nèi),我們?nèi)绾芜x擇珠插,才能使得物品的總價格最高惧磺。


0 - 1 背包問題背后的圖

0-1背包.png

解法思路(一)記憶化遞歸

問題狀態(tài)的定義
  • F(n, C) 考慮將 n 個物品,放進容量為 C 的背包捻撑,是價值最大磨隘;
價值最大的兩種情況
  • n - 1 個元素不放進背包中,背包中的價值最大顾患,對應的狀態(tài)為:F(n-2, C)番捂;
  • n - 1 個元素放進背包中,背包中的價值最大江解,對應的狀態(tài)為:v[n-1] + F(n-2, C-w[n-1]设预;
哪種情況的價值最大?
  • max( F(n-2, C), v[n-1] + F(n-2, C-w[n-1]) )

解法實現(xiàn)(一)記憶化遞歸

實現(xiàn)細節(jié)
  • 記憶數(shù)組 memo 是二維的犁河;
package knapsack;

public class Solution1 {

    private int[][] memo;

    public int knapsack01(int[] w, int[] v, int C){

        if(w == null || v == null || w.length != v.length)
            throw new IllegalArgumentException("Invalid w or v");

        if(C < 0)
            throw new IllegalArgumentException("C must be greater or equal to zero.");

        int n = w.length;
        if(n == 0 || C == 0)
            return 0;

        memo = new int[n][C + 1];
        return bestValue(w, v, n - 1, C);
    }

    // 用 [0...index] 的物品,填充容積為c 的背包的最大價值
    private int bestValue(int[] w, int[] v, int index, int c){

        if(c <= 0 || index < 0)
            return 0;

        if(memo[index][c] != -1)
            return memo[index][c];

        int res = bestValue(w, v, index-1, c);
        if(c >= w[index])
            res = Math.max(res,
                    v[index] + bestValue(w, v, index - 1, c - w[index]));

        return memo[index][c] = res;
    }

}

解法思路(二)動態(tài)規(guī)劃

思路描述
  • 把頭 1 個元素放進背包中鳖枕,最大價值就是數(shù)組中 memo[0][C]
  • 把頭 2 個元素放進背包中桨螺,最大價值就是數(shù)組中 memo[1][C]宾符;
  • ...
  • 把頭 n 個元素放進背包中,最大價值就是數(shù)組中 memo[n - 1][C]灭翔;
memo 數(shù)組怎么填吸奴?
  • 從左上角到右下角一行一行一格一格的填;
  • 第一行的填法是:容量比 w[0] 小的的格都填 0缠局,其余都填 v[0]
  • 第二行開始的填法:
    • 容量比 w[i] 小的格考润,把頭頂上那格 memo[i-1][j] 的值落下來狭园;
    • 容量大于等于 w[i] 的格,比較 memo[i-1][j]v[i] + memo[i - 1][j - w[i]] 的值的大泻巍唱矛;

解法實現(xiàn)(二)動態(tài)規(guī)劃

實現(xiàn)細節(jié)
  • 從數(shù)組 memo 的左上開始填,一直填到右下,右下角的值就是背包問題的題解绎谦;
package knapsack;

public class Solution2 {

    public int knapsack01(int[] w, int[] v, int C){

        if(w == null || v == null || w.length != v.length)
            throw new IllegalArgumentException("Invalid w or v");

        if(C < 0)
            throw new IllegalArgumentException("C must be greater or equal to zero.");

        int n = w.length;
        if(n == 0 || C == 0)
            return 0;

        int[][] memo = new int[n][C + 1];

        for(int j = 0 ; j <= C ; j ++)
            memo[0][j] = (j >= w[0] ? v[0] : 0 );

        for(int i = 1 ; i < n ; i ++)
            for(int j = 0 ; j <= C ; j ++){
                memo[i][j] = memo[i-1][j];
                if(j >= w[i])
                    memo[i][j] = Math.max(memo[i][j], v[i] + memo[i - 1][j - w[i]]);
            }

        return memo[n - 1][C];
    }

}

解法實現(xiàn)(三)動態(tài)規(guī)劃 優(yōu)化

優(yōu)化結果
  • 空間復雜度從 n * C 變成 2 * C管闷;
package knapsack;

public class Solution3 {

    public int knapsack01(int[] w, int[] v, int C){

        if(w == null || v == null || w.length != v.length)
            throw new IllegalArgumentException("Invalid w or v");

        if(C < 0)
            throw new IllegalArgumentException("C must be greater or equal to zero.");

        int n = w.length;
        if(n == 0 || C == 0)
            return 0;

        int[][] memo = new int[2][C + 1];

        for(int j = 0 ; j <= C ; j ++)
            memo[0][j] = (j >= w[0] ? v[0] : 0);

        for(int i = 1 ; i < n ; i ++)
            for(int j = 0 ; j <= C ; j ++){
                memo[i % 2][j] = memo[(i-1) % 2][j];
                if(j >= w[i])
                    memo[i % 2][j] = Math.max(memo[i % 2][j], v[i] + memo[(i-1) % 2][j - w[i]]);
            }

        return memo[(n-1) % 2][C];
    }

}

解法實現(xiàn)(四)動態(tài)規(guī)劃 優(yōu)化

優(yōu)化結果
  • 空間復雜度從 2 * C 變成 C
package knapsack;

public class Solution4 {

    public int knapsack01(int[] w, int[] v, int C){

        if(w == null || v == null || w.length != v.length)
            throw new IllegalArgumentException("Invalid w or v");

        if(C < 0)
            throw new IllegalArgumentException("C must be greater or equal to zero.");

        int n = w.length;
        if(n == 0 || C == 0)
            return 0;

        int[] memo = new int[C+1];

        for(int j = 0 ; j <= C ; j ++)
            memo[j] = (j >= w[0] ? v[0] : 0);

        for(int i = 1 ; i < n ; i ++)
            for(int j = C ; j >= w[i] ; j --)
                memo[j] = Math.max(memo[j], v[i] + memo[j - w[i]]);

        return memo[C];
    }

}

算法 [Java] 目錄

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窃肠,一起剝皮案震驚了整個濱河市包个,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冤留,老刑警劉巖碧囊,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異纤怒,居然都是意外死亡糯而,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門泊窘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來熄驼,“玉大人,你說我怎么就攤上這事烘豹」霞郑” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵吴叶,是天一觀的道長阐虚。 經(jīng)常有香客問我,道長蚌卤,這世上最難降的妖魔是什么实束? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮逊彭,結果婚禮上咸灿,老公的妹妹穿的比我還像新娘。我一直安慰自己侮叮,他們只是感情好避矢,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著囊榜,像睡著了一般审胸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上卸勺,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天砂沛,我揣著相機與錄音,去河邊找鬼曙求。 笑死碍庵,一個胖子當著我的面吹牛映企,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播静浴,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼堰氓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了苹享?” 一聲冷哼從身側響起双絮,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎富稻,沒想到半個月后掷邦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡椭赋,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年抚岗,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哪怔。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡宣蔚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出认境,到底是詐尸還是另有隱情胚委,我是刑警寧澤,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布叉信,位于F島的核電站亩冬,受9級特大地震影響,放射性物質發(fā)生泄漏硼身。R本人自食惡果不足惜硅急,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望佳遂。 院中可真熱鬧营袜,春花似錦、人聲如沸丑罪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吩屹。三九已至跪另,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間煤搜,已是汗流浹背免绿。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留宅楞,地道東北人针姿。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像厌衙,于是被迫代替她去往敵國和親距淫。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355