揹包問題及其變體(Java)

揹包問題:

最經典的0-1揹包
揹包問題是有一個一定容量的揹包,然後有一堆物品,這些物品有一定的重量,要求在不超過揹包容量的情況下,裝最多的重量的問題。

揹包問題的變體
揹包問題有很多的變體,主要有以下幾個:

  • 物品不僅僅有重量還會對應不同的價值,求價值最高,而不是重量最高
  • 每個物品有無限個而不是隻有一個
  • 不是求最大重量,而是求達到重量的組合的個數

揹包問題的解法:

揹包問題是用動態規劃的方法解決,可以構造一個二維動規數組解決問題,而二維數組的做法可以進一步優化空間複雜度變爲一維的動規數組。

動態規劃的問題主要要解決三個問題,1、dp數組的設計,2、邊界情況,3、遞推公式。

二維動規劃數組的解法

  • 動態規劃數組設置:設置大小爲dp[items.length + 1][backpackSize + 1]的boolean數組,其中數組中每個元素dp[i][j]表示只用前i個物品,能否達到j的負重。+1是用於設置邊界條件。
  • 邊界情況:邊界條件是在考慮0件物品 和 揹包負重爲0 時最大負重爲0。
  • 遞推公式:
    • dp[i][j] = dp[i - 1][j] 只要用前i-1個物品能達到 j 重量,那用上物品i,也能達到j重量
    • j >= items[i-1] && dp[i-1][j - items[i-1]] 時,dp[i][j] 設置爲true
      保證揹包的負重大於物品i(這裏-1是因爲0用於設置邊界,從1開始),證明當前負重能放下物品i,如果在不用i物品時,可以達到負重j減物品i的重量,那麼用上了物品i,就可以達到j的重量。
  • 具體的解法看下題 lintcode 92

一維度數組
也是考慮動規數組的設計、邊界情況、遞推公式三個問題,二維數組優化爲一維,是滾動重複運用了一維數組,達到二維的效果,它們都要兩重循環,時間是一樣的。

  • 動規數組:設置大小爲dp[backpackSize + 1]的boolean數組,dp[j]表示:能否達到j負重
  • 邊界情況:dp[0] = 0
  • 遞推公式:同樣是遍歷各個物體i,在只考慮i-1的物品時,如果能達到dp[j - items[i]],那麼考慮了物品i,可以達到負重j。
    這裏注意,因爲每個物品只能用一次,所以應該從最大負重往前遞推,如果從前往後遞推,物品就可能被使用了多次。
  • 同樣結合lintcode92 看完整的解法

揹包變體的解法

  • 帶價值的物品:遞推數組不要設置爲boolean,設置爲int數組,表示價值
    • 二維:dp[i][j]表示,只考慮前i的物品,負重不超過j的情況下,價值最高的值
    • 一維:dp[j] 表示:不超過負重j的情況下,價值最高的值得
    • 結合lintcode 125 看完整解法
  • 物品不是隻有一個,而是有無限個:只需要把遞推的方向反轉,剛剛提到最大負重往前遞推,防止多次使用到物體,只要正向遞推,就可以解決無限個物體的問題,具體看lintcode 440 完整解法
  • 求組合個數:只要把動規數組的值,設置爲達到這個重量的組合個數,邊界情況dp[0]=1 即可,具體看 lintcode 563

lintcode 92 揹包問題

這是經典的01揹包問題:

二維數組的動態規劃解法:

public int backPack(int backpackSize, int[] items) {

        // 由於要從揹包爲0,物體數爲0的狀態開始,所以dp數組要多一行的維度
        boolean dp[][] = new boolean[items.length + 1][backpackSize + 1];

        for (int i = 0; i <= items.length; i++) {
            for (int j = 0; j <= backpackSize; j++) {
                dp[i][j] = false;
            }
        }

        // dp[i][j] 表示 前i個揹包,隨機選若干個,能不能達到j的重量
        dp[0][0] = true;
        for (int i = 1; i <= items.length; i++) {
            for (int j = 0; j <= backpackSize; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= items[i-1] && dp[i-1][j - items[i-1]]) {
                    dp[i][j] = true;
                }
            }
        }

        for (int i = backpackSize; i >= 0; i--) {
            if (dp[items.length][i]) {
                return i;
            }
        }

        return 0;
    }

一維滾動數組解法:

public int backPack(int backpackSize, int[] items) {
        
        // dp[i]代表,容量爲 i 的揹包,最否能裝到負重j
        boolean dp[] = new boolean[backpackSize + 1];

		dp[0] = true;
        for(int i = 0; i < items.length; i++) {
            for(int j = backpackSize; j >= items[i]; j--) {
                if(dp[j - items[i]]){
                	dp[j] = true;
                }
            }
        }
        
        for(int j = backpackSize; j >= 0; j--){
        	if(dp[j]){
        		return j;
        	}
        }
        return 0;
    }

lintcode 125 帶價值的揹包問題

這是01揹包中物品帶有價值的變體

只要把01揹包問題中,dp數組的值設置爲當前最大價值,而非boolean能否得到。稍微改動即可

二維數組解法:

public int backPackII(int backpackSize, int[] items, int[] itemsValues) {
        // write your code here

        int[][] dp = new int[items.length + 1][backpackSize + 1];

        for(int i = 0; i <= items.length; i++){
            for(int j = 0; j <= backpackSize; j++){
                if(i == 0 || j == 0){
                    dp[i][j] = 0;
                }else if(items[i-1] > j){
                    dp[i][j] = dp[i-1][j];
                }else{
                    dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-items[i-1]] + itemsValues[i-1]);
                }

            }
        }

        return dp[items.length][backpackSize];
    }

一維滾動數組:

public int backPackII2(int backpackSize, int[] items, int[] itemsValues) {
        // write your code here

        int[] dp = new int[backpackSize + 1];

        for(int i = 0; i < items.length; i++){
            for(int j = backpackSize; j >= items[i]; j--){
                if(dp[j] < dp[j - items[i]] + itemsValues[i]){
                    dp[j] = dp[j - items[i]] + itemsValues[i];
                }
            }
        }

        return dp[backpackSize];
    }

lintcode 440. 揹包問題 III

這是01揹包問題物體可以用無限次,且帶有價值的變體

dp數組爲當前最大價值,遞推公式從前往後遞推,無限次使用物品即可。

一維滾動數組

public int backPackIII(int[] items, int[] itemsValues, int backpackSize) {

        int[] dp = new int[backpackSize + 1];

        for(int i = 0; i < items.length; i++){
            for(int j = items[i]; j <= backpackSize; j++){
                if(dp[j] < dp[j - items[i]] + itemsValues[i]){
                    dp[j] = dp[j - items[i]] + itemsValues[i];
                }
            }
        }

        return dp[backpackSize];
    }

lintcode 563. 揹包問題 V

這是求達到target負重的組合個數,物品只能用一次

只要邊界情況設置爲1,然後把dp的值,變爲當前的組合個數,遞推時不是是否能達到的boolean,而是累加上前狀態的個數即可。

public int backPackV(int[] nums, int target) {
        // Write your code here
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int i = 0; i < nums.length; ++i)
            for (int  j = target; j >= nums[i]; --j)
                dp[j] += dp[j - nums[i]];

        return dp[target];
    }

lintcode 562. 揹包問題 IV

這是求達到target負重的組合個數,物品只能用無限次

只要只能用一次的稍微修改,改爲從前向後遞推即可。

public int backPackIV(int[] nums, int target) {
        // write your code here
        int[] dp = new int[target + 1];
        
        dp[0] = 1;
        for(int i = 0; i < nums.length; i++){
            for(int j = nums[i]; j <= target; j++){
                dp[j] += dp[j - nums[i]];
            }
        }
        
        return dp[target];
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章