0-1揹包問題從不懂到運用

一. 理解

  1. 0-1揹包問題是個什麼問題

    一個小偷帶了一個只能容納C的揹包來店裏偷東西,店裏有n個商品,每個商品有重量和價值,n(w,v)
    問小偷能偷到的最大價值

    推薦一個視頻,看完了可以很好的理解什麼是揹包問題,以及解決揹包問題的思路、思想

    https://video.tudou.com/v/XMTc5MjQ3NTg5Mg==.html?__fr=oldtd

  2. 在視頻裏面主要就是學會思想,實際代碼操作還是有點不同的,需要自己動手寫一篇並加以理解

    	/**
         * 使用二維數組接受每個結點的值,該節點表示容量爲w時,第k個及之前的商品的最大價值;int[k][w]
         * @param weight 商品重量列表
         * @param value  商品價值列表,所以這裏weight和value的長度相等,一一對應
         * @param cap    小偷所帶揹包的容量
         * @return 小偷所能偷到的最大價值
         */
        public int backpack(int[] weight, int[] value, int cap) {
            int length = weight.length;
            int[][] ints = new int[length][cap + 1];
            // 填充第一行,因爲後面的循環第一行無法計算到
            for (int i = 0; i <= cap; i++) {
                ints[0][i] = i < weight[0] ? 0 : value[0];
            }
            for (int i = 1; i < length; i++) {
                for (int j = 0; j <= cap; j++) {
                    // 小偷的揹包裝不下
                    if (j < weight[i]) {
                        ints[i][j] = ints[i - 1][j];
                    } else {
                        // 小偷選擇裝不裝該商品的兩種情況
                        ints[i][j] = Math.max(ints[i - 1][j], ints[i - 1][j - weight[i]] + value[i]);
                    }
                }
            }
            return ints[length - 1][cap];
        }
    
  3. 優化,空間複雜度的優化:
    將二維數組壓縮成一維數組,利用記憶(一維數組存儲的每行都會被替換掉)的特點
    因爲由前面的公式可以知道,求 F(i,j)=max( F(i-1,j) , F(i-1,j-w(i))+value(i) ),即需要知道上一行的兩個數,j和j-w(i)
    利用記憶的特點也就是,當前行的 本身 和 j-w(i) 兩個數
    一維代替二維,就是一維的多次複用
    還有一點需要注意的是:替換的話,遍歷每行的時候,必須從後往前,不然不會取到之前的值

    	/**
         * 空間極致化,將二維數組壓縮成一維數組,利用記憶(一維數組存儲的每行都會被替換掉)的特點
         * 因爲由前面的公式可以知道,求 F(i,j)=max( F(i-1,j) , F(i-1,j-w(i))+value(i) ),即需要知道上一行的兩個數
         * 利用記憶的特點也就是,當前行的 本身 和 j-w(i) 兩個數
         * 一維代替二維,就是一維的多次複用
         * 還有一點需要注意的是:替換的話,遍歷每行的時候,必須從後往前,不然不會取到之前的值
         */
        public int backpackByOneArray(int[] weight, int[] value, int cap) {
            int length = weight.length;
            int[] ints = new int[cap + 1];    
            for (int i = 0; i < length; i++) {
                for (int j = cap; j >= weight[i]; j--) {
                    ints[j] = Math.max(ints[j], ints[j - weight[i]] + value[i]);
                }
            }
            return ints[cap];
        }
    

二. 應用

1. LeetCode算法題第416: 分割等和子集


給定一個只包含正整數的非空數組。是否可以將這個數組分割成兩個子集,使得兩個子集的元素和相等

問題轉換:0-1揹包問題,數組相當於商品,商品的重量和價值相等,揹包的容量就是sum/2,看能不能求得出最大價值等於揹包容量判斷有沒有指定和

	/**
     * 這是我自己根據揹包問題看出來的解法
     */
    public boolean canPartition2(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        // 奇數
        if ((sum & 1) == 1) {
            return false;
        }
        sum = sum >> 1;
        // 問題轉換:0-1揹包問題,數組相當於商品,商品的重量和價值相等,揹包的容量就是sum,看能不能求得出最大價值等於揹包容量判斷有沒有指定和
        int length = nums.length;
        int[] ints = new int[sum + 1];  
        for (int i = 0; i < length; i++) {
            for (int j = sum; j >= nums[i]; j--) {
                ints[j] = Math.max(ints[j], ints[j - nums[i]] + nums[i]);
            }
        }
        return ints[sum] == sum;
    }

在這裏插入圖片描述
還有一種更簡潔的寫法,

	/**
     * 這是我自己根據揹包問題看出來的解法
     */
    public boolean canPartition2(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        // 奇數
        if ((sum & 1) == 1) {
            return false;
        }
        sum = sum >> 1;
        // 問題轉換:0-1揹包問題,數組相當於商品,商品的重量和價值相等,揹包的容量就是sum,看是否可以剛好裝滿揹包
        boolean[] result = new boolean[sum + 1];
        // 揹包容量爲0的時候,肯定是true,因爲可以什麼都不裝(剛好裝滿)
        result[0] = true;
        for (int num : nums) {
            for (int j = sum; j >= num; j--) {
                // 容量爲j的時候,是否可以剛好裝滿,兩種情況:1.不裝當前數,2.裝當前數;兩種情況下,只需要滿足一種,即可達到要求
                result[j] = result[j] || result[j - num];
            }
        }
        return result[sum];
  1. 給定一個整數數組和一個整數,問能否在數組中找到任意數之和等於該整數(每個數只能用一次)
    問題轉換:揹包問題,數組相當於商品,商品的價格和重量相等(爲數組的值),待求和的整數相當於揹包容量
    最後:看求出來的最大價值跟揹包容量是否一致即可判斷

    	 /**
         * 問題轉換:揹包問題,數組相當於商品,商品的價格和重量相等(爲數組的值),待求和的整數相當於揹包容量
         * 最後:看求出來的最大價值跟揹包容量是否一致即可判斷
         */
        public boolean ifCanFindArraySum(int[] nums, int sum) {
            if (nums == null || nums.length == 0) {
                return false;
            }
            int[] result = new int[sum + 1];
            for (int num : nums) {
                for (int j = sum; j >= num; j--) {
                    // 兩種情況
                    result[j] = Math.max(result[j], result[j - num] + num);
                }
            }
            return result[sum] == sum;
        }
    

    其實跟LeetCode那一題很相似
    第二種寫法類似

  2. 給定一個整數數組和一個整數,問在數組中任意數之和等於該整數的組合方式有多少種(每個數只能用一次)
    轉換爲:揹包問題,揹包容量sumP,數組元素商品,元素值商品重量,求剛好裝滿揹包的商品組合數

    public int findTargetSumWays(int[] nums, int sum) {
            int[] result = new int[sum + 1];
            // 和等於0有一種方式
            result[0] = 1;
            for (int num : nums) {
                for (int j = sum; j >= num; j--) {
                    // 和等於j:兩種情況,1.不需要當前數,2.需要當前數(和等於j-當前數)
                    result[j] += result[j - num];
                }
            }
            return result[sum];
        }
    
  3. 揹包問題總結:

	/**
     * 揹包問題,其實是一類問題(要不要當前數,揹包容量轉數組),並不是侷限於求最大價值(數組形式不侷限),
     * 使用的時候關鍵是要找到遞歸的條件,即要不要當前數的兩種情況該怎麼組和成結果
     * 如:
     *     最大價值,max(兩種情況)
     *     是否可以剛好裝滿:||(兩種情況)
     *     剛好裝滿的組合數:+(兩種情況)
     */

遇到時還需要靈活運用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章