背包問題合集

背包問題

  1. 判斷是排列問題 還是 組合問題
  2. 確定遍歷順序:
  • 如果求組合數(shù)就是外層for循環(huán)遍歷物品,內(nèi)層for遍歷背包惠勒。
  • 如果求排列數(shù)就是外層for遍歷背包赚抡,內(nèi)層for循環(huán)遍歷物品。
    解釋:如果把遍歷nums(物品)放在外循環(huán)纠屋,遍歷target的作為內(nèi)循環(huán)的話涂臣,舉一個(gè)例子:計(jì)算dp[4]的時(shí)候,結(jié)果集只有 {1,3} 這樣的集合售担,不會(huì)有{3,1}這樣的集合赁遗,因?yàn)閚ums遍歷放在外層,3只能出現(xiàn)在1后面族铆!
[圖片上傳中...(截屏2021-12-07 上午9.31.40.png-8635f4-1638840704126-0)]

截屏2021-12-07 上午9.32.28.png

https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md#0-1-%E8%83%8C%E5%8C%85

https://leetcode-cn.com/problems/coin-change/solution/yi-pian-wen-zhang-chi-tou-bei-bao-wen-ti-sq9n/

背包問題力扣完整攻略
只要按如下順序刷題岩四,相信會(huì)幫你在學(xué)習(xí)背包問題的路上少走很多彎路!

「力扣」上的 0-1 背包問題

416.分割等和子集
474.一和零
494.目標(biāo)和
879 :盈利計(jì)劃(困難)
1049.最后一塊石頭的重量 II

「力扣」上的 完全背包問題:

https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486107&idx=1&sn=e5fa523008fc5588737b7ed801caf4c3&chksm=fd9ca184caeb28926959c0987208a3932ed9c965267ed366b5b82a6fc16d42f1ff40c29db5f1&token=990510480&lang=zh_CN&scene=21#wechat_redirect

  1. 零錢兌換 II
    377.組合總和Ⅳ. !!不是「完全背包」問題
    70.爬樓梯進(jìn)階版
  2. 零錢兌換
    279.完全平方數(shù)
    139.單詞拆分
    第 1449 題:數(shù)位成本和為目標(biāo)值的最大數(shù)字(困難)

「力扣」上的 多重背包問題:

https://mp.weixin.qq.com/s?__biz=MzU4NDE3MTEyMA==&mid=2247486649&idx=1&sn=ba09ee2d78377c2ddbb9e43622880133&chksm=fd9ca7a6caeb2eb0db61b7604a4aaa8d3ca90d6bc05eb6f50aaab415c4bd7f0047c1ca591018&token=1008907671&lang=zh_CN&scene=21#wechat_redirect

1. 416.分割等和子集

給你一個(gè) 只包含正整數(shù) 的 非空 數(shù)組 nums 哥攘。請(qǐng)你判斷是否可以將這個(gè)數(shù)組分割成兩個(gè)子集剖煌,使得兩個(gè)子集的元素和相等。
示例 1:
輸入:nums = [1,5,11,5]
輸出:true
解釋:數(shù)組可以分割成 [1, 5, 5] 和 [11] 逝淹。
【0-1背包存在性問題:是否存在一個(gè)子集,其和為target=sum/2,外循環(huán)nums,內(nèi)循環(huán)target倒序】

public class Solution {

    public boolean canPartition(int[] nums) {
        int len = nums.length;
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        if ((sum & 1) == 1) {
            return false;
        }

        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;

        if (nums[0] <= target) {
            dp[nums[0]] = true;
        }
        for (int i = 1; i < len; i++) {
            for (int j = target; nums[i] <= j; j--) {
                if (dp[target]) {
                    return true;
                }
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[target];
    }
}
//時(shí)間復(fù)雜度:O(NC)O(NC):這里 NN 是數(shù)組元素的個(gè)數(shù)末捣,CC 是數(shù)組元素的和的一半;
//空間復(fù)雜度:O(C)O(C):減少了物品那個(gè)維度创橄,無論來多少個(gè)數(shù)箩做,用一行表示狀態(tài)就夠了。

2. 474. 給你一個(gè)二進(jìn)制字符串?dāng)?shù)組 strs 和兩個(gè)整數(shù) m 和 n 妥畏。

請(qǐng)你找出并返回 strs 的最大子集的長(zhǎng)度邦邦,該子集中 最多 有 m 個(gè) 0 和 n 個(gè) 1 安吁。
如果 x 的所有元素也是 y 的元素击蹲,集合 x 是集合 y 的 子集 逞壁。
示例 1:
輸入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
輸出:4
解釋:最多有 5 個(gè) 0 和 3 個(gè) 1 的最大子集是 {"10","0001","1","0"} 挟鸠,因此答案是 4 缭嫡。
其他滿足題意但較小的子集包括 {"0001","1"} 和 {"10","1","0"} 钓觉。{"111001"} 不滿足題意犁罩,因?yàn)樗?4 個(gè) 1 拙友,大于 n 的值 3 辟癌。
解析:
通常與「背包問題」相關(guān)的題考察的是 將原問題轉(zhuǎn)換為「背包問題」的能力氏身。
要將原問題轉(zhuǎn)換為「背包問題」巍棱,往往需要從題目中抽象出「價(jià)值」與「成本」的概念。
這道題如果抽象成「背包問題」的話蛋欣,應(yīng)該是:
每個(gè)字符串的價(jià)值都是 1(對(duì)答案的貢獻(xiàn)都是 1)航徙,選擇的成本是該字符串中 1 的數(shù)量和 0 的數(shù)量。


截屏2021-12-07 上午11.00.02.png

以上是原始狀態(tài)轉(zhuǎn)義公式陷虎,下面代碼是經(jīng)過空間優(yōu)化后的:

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int len = strs.length;
        int[][] cnt = new int[len][2];
        for (int i = 0; i < len; i++) {
            int zero = 0, one = 0;
            for (char c : strs[i].toCharArray()) {
                if (c == '0') zero++;
                else one++;
            }
            cnt[i] = new int[]{zero, one};
        }
        int[][] f = new int[m + 1][n + 1];
        for (int k = 0; k < len; k++) {
            int zero = cnt[k][0], one = cnt[k][1];
            for (int i = m; i >= zero; i--) {
                for (int j = n; j >= one; j--) {
                    f[i][j] = Math.max(f[i][j], f[i - zero][j - one] + 1);
                }
            }
        }
        return f[m][n];
    }
}
時(shí)間復(fù)雜度:O(k?m?n)
空間復(fù)雜度:O(m * n)

3. 494 目標(biāo)和

給你一個(gè)整數(shù)數(shù)組 nums 和一個(gè)整數(shù) target 到踏。
向數(shù)組中的每個(gè)整數(shù)前添加 '+' 或 '-' ,然后串聯(lián)起所有整數(shù)尚猿,可以構(gòu)造一個(gè) 表達(dá)式 :
例如窝稿,nums = [2, 1] ,可以在 2 之前添加 '+' 凿掂,在 1 之前添加 '-' 讹躯,然后串聯(lián)起來得到表達(dá)式 "+2-1" 。
返回可以通過上述方法構(gòu)造的缠劝、運(yùn)算結(jié)果等于 target 的不同 表達(dá)式 的數(shù)目潮梯。
示例 1:
輸入:nums = [1,1,1,1,1], target = 3
輸出:5
解釋:一共有 5 種方法讓最終目標(biāo)和為 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
解析:
【0-1背包不考慮元素順序的組合問題】
當(dāng)使用遞推形式時(shí)惨恭,我們通常會(huì)使用「靜態(tài)數(shù)組」來存儲(chǔ)動(dòng)規(guī)值秉馏,因此還需要考慮維度范圍的:
第一維為物品數(shù)量:范圍為 nums 數(shù)組長(zhǎng)度
第二維為中間結(jié)果:令 s 為所有 nums 元素的總和(題目給定了 nums[i] 為非負(fù)數(shù)的條件,否則需要對(duì) nums[i] 取絕對(duì)值再累加)脱羡,那么中間結(jié)果的范圍為 [-s, s]

   class Solution {
    public int findTargetSumWays(int[] nums, int t) {
        int n = nums.length;
        int s = 0;
        for (int i : nums) s += Math.abs(i);
        if (Math.abs(t) > s) return 0;
        int[][] f = new int[n + 1][2 * s + 1];
        f[0][0 + s] = 1;
        for (int i = 1; i <= n; i++) {
            int x = nums[i - 1];
            for (int j = -s; j <= s; j++) {
                if ((j - x) + s >= 0) f[i][j + s] += f[i - 1][(j - x) + s];
                if ((j + x) + s <= 2 * s) f[i][j + s] += f[i - 1][(j + x) + s];
            }
        }
        return f[n][t + s];
    }
}

4. 879. 盈利計(jì)劃

截屏2021-12-07 下午2.29.40.png
截屏2021-12-07 下午2.28.41.png
class Solution {
    int mod = (int)1e9+7;
    public int profitableSchemes(int n, int min, int[] gs, int[] ps) {
        int m = gs.length;
        long[][][] f = new long[m + 1][n + 1][min + 1];
        for (int i = 0; i <= n; i++) f[0][i][0] = 1;            
        for (int i = 1; i <= m; i++) {
            int a = gs[i - 1], b = ps[i - 1];
            for (int j = 0; j <= n; j++) {
                for (int k = 0; k <= min; k++) {
                    f[i][j][k] = f[i - 1][j][k];
                    if (j >= a) {
                        int u = Math.max(k - b, 0);
                        f[i][j][k] += f[i - 1][j - a][u];
                        if (f[i][j][k] >= mod) f[i][j][k] -= mod;
                    }
                }
            }
        }
        return (int)f[m][n][min]; 
    }
}

空間優(yōu)化版本:

class Solution {
    int mod = (int)1e9+7;
    public int profitableSchemes(int n, int min, int[] gs, int[] ps) {
        int m = gs.length;
        int[][] f = new int[n + 1][min + 1];
        for (int i = 0; i <= n; i++) f[i][0] = 1;            
        for (int i = 1; i <= m; i++) {
            int a = gs[i - 1], b = ps[i - 1];
            for (int j = n; j >= a; j--) {
                for (int k = min; k >= 0; k--) {
                    int u = Math.max(k - b, 0);
                    f[j][k] += f[j - a][u];
                    if (f[j][k] >= mod) f[j][k] -= mod;
                }
            }
        }
        return f[n][min]; 
    }
}

5. 518. 零錢兌換II (Coin Change 2)

給你一個(gè)整數(shù)數(shù)組 coins 表示不同面額的硬幣萝究,另給一個(gè)整數(shù) amount 表示總金額。
請(qǐng)你計(jì)算并返回可以湊成總金額的硬幣組合數(shù)锉罐。如果任何硬幣組合都無法湊出總金額帆竹,返回 0 。
假設(shè)每一種面額的硬幣有無限個(gè)脓规。
題目數(shù)據(jù)保證結(jié)果符合 32 位帶符號(hào)整數(shù)栽连。
示例 1:
輸入:amount = 5, coins = [1, 2, 5]
輸出:4
解釋:有四種方式可以湊成總金額:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1
解釋:完全背包問題
定義 f[i][j]為考慮前 ii 件物品,湊成總和為 j 的方案數(shù)量。
為了方便初始化秒紧,我們一般讓f[0][x] 代表不考慮任何物品的情況绢陌。
因此我們有顯而易見的初始化條件:f[0][0] = 1,其余 f[0][x] = 0熔恢。
代表當(dāng)沒有任何硬幣的時(shí)候脐湾,存在湊成總和為 0 的方案數(shù)量為 1;湊成其他總和的方案不存在叙淌。
【完全背包不考慮順序的組合問題】

class Solution {
    public int change(int cnt, int[] cs) {
        int n = cs.length;
        int[] f = new int[cnt + 1];
        f[0] = 1;
        for (int i = 1; i <= n; i++) {
            int val = cs[i - 1];
            for (int j = val; j <= cnt; j++) {
                f[j] += f[j - val];
            }
        }
        return f[cnt];
    }
}

6. 322. 零錢兌換

給你一個(gè)整數(shù)數(shù)組 coins 秤掌,表示不同面額的硬幣;以及一個(gè)整數(shù) amount 鹰霍,表示總金額闻鉴。
計(jì)算并返回可以湊成總金額所需的 最少的硬幣個(gè)數(shù) 。如果沒有任何一種硬幣組合能組成總金額衅谷,返回 -1 椒拗。
你可以認(rèn)為每種硬幣的數(shù)量是無限的似将。
示例 1:
輸入:coins = [1, 2, 5], amount = 11
輸出:3
解釋:11 = 5 + 5 + 1
【完全背包最值問題:外循環(huán)coins,內(nèi)循環(huán)amount正序】

int coinChange(vector<int> &coins, int amount)
{
    vector<long long> dp(amount, INT_MAX); //給dp數(shù)組每個(gè)位置賦初值為INT_MAX是為了最后判斷是否能填滿amount,要用long long 類型
    dp[0] = 0;  //dp[i]:換到面值i所用的最小數(shù)量
    for (int coin : coins)
    {
        for (int i = coin; i <= amount; i++)
        {
                dp[i] = min(dp[i], dp[i - coin] + 1);
        }
    }
    return dp[amount] == INT_MAX ? -1 : dp[amount];
}

7. 279. 完全平方數(shù)

給你一個(gè)整數(shù) n 获黔,返回和為 n 的完全平方數(shù)的 最少數(shù)量
完全平方數(shù) 是一個(gè)整數(shù)在验,其值等于另一個(gè)整數(shù)的平方玷氏;換句話說,其值等于一個(gè)整數(shù)自乘的積腋舌。例如盏触,14块饺、916 都是完全平方數(shù)赞辩,而 311 不是。
示例 1:
輸入:n = 12
輸出:3
解釋:12 = 4 + 4 + 4
完全背包的最值問題:完全平方數(shù)最小為1,最大為sqrt(n),故題目轉(zhuǎn)換為在nums=[1,2.....sqrt(n)]中選任意數(shù)平方和為target=n
外循環(huán)nums,內(nèi)循環(huán)target正序,應(yīng)用轉(zhuǎn)移方程1

int numSquares(int n)
{
    vector<int> dp(n + 1, INT_MAX); //dp[i]:和為i的完全平方數(shù)的最小數(shù)量
    dp[0] = 0;
    for (int num = 1; num <= sqrt(n); num++)
    {
        for (int i = 0; i <= n; i++)
        {
            if (i >= num * num)
                dp[i] = min(dp[i], dp[i - num * num] + 1);
        }
    }
    return dp[n];
}

8. 377. 組合總和 Ⅳ

【完全背包問題的排列問題】在nums中任選一些數(shù),和為target
考慮順序的組合問題:外循環(huán)target,內(nèi)循環(huán)nums,應(yīng)用狀態(tài)方程3
示例 1:
輸入:nums = [1,2,3], target = 4
輸出:7
解釋:
所有可能的組合為:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
請(qǐng)注意授艰,順序不同的序列被視作不同的組合辨嗽。

int combinationSum4(vector<int> &nums, int target)
{
    vector<int> dp(target + 1);
    dp[0] = 1;
    for (int i = 1; i <= target; i++)
    {
        for (int num : nums)
        {
            if (num <= i) 
                dp[i] += dp[i - num];
        }
    }
    return dp[target];
}

9. 1155. 擲骰子的N種方法

投擲骰子的方法數(shù):d個(gè)骰子,每個(gè)有f個(gè)面(點(diǎn)數(shù)為1,2,...f),求骰子點(diǎn)數(shù)和為target的方法
分組0/1背包的組合問題:dp[i][j]表示投擲i個(gè)骰子點(diǎn)數(shù)和為j的方法數(shù);三層循環(huán):最外層為背包d,然后先遍歷target后遍歷點(diǎn)數(shù)f
應(yīng)用二維拓展的轉(zhuǎn)移方程3:dp[i][j]+=dp[i-1][j-f];

int numRollsToTarget(int d, int f, int target)
{
    vector<vector<int>> dp(d + 1, vector<int>(target + 1, 0));
    dp[0][0] = 1;
    for (int i = 1; i <= d; i++)
        for (int j = 1; j <= target; j++)
            for (int k = 1; k <= f && j >= k; k++)
                dp[i][j] += dp[i - 1][j - k];
    return dp[d][target];
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市淮腾,隨后出現(xiàn)的幾起案子糟需,更是在濱河造成了極大的恐慌,老刑警劉巖谷朝,帶你破解...
    沈念sama閱讀 211,265評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洲押,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡圆凰,警方通過查閱死者的電腦和手機(jī)杈帐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來专钉,“玉大人娘荡,你說我怎么就攤上這事干旁。” “怎么了炮沐?”我有些...
    開封第一講書人閱讀 156,852評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵争群,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我大年,道長(zhǎng)换薄,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評(píng)論 1 283
  • 正文 為了忘掉前任翔试,我火速辦了婚禮轻要,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘垦缅。我一直安慰自己冲泥,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評(píng)論 5 384
  • 文/花漫 我一把揭開白布壁涎。 她就那樣靜靜地躺著凡恍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怔球。 梳的紋絲不亂的頭發(fā)上嚼酝,一...
    開封第一講書人閱讀 49,772評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音竟坛,去河邊找鬼闽巩。 笑死,一個(gè)胖子當(dāng)著我的面吹牛担汤,可吹牛的內(nèi)容都是我干的涎跨。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼崭歧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼隅很!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起驾荣,我...
    開封第一講書人閱讀 37,688評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤外构,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后播掷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體审编,經(jīng)...
    沈念sama閱讀 44,130評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評(píng)論 2 325
  • 正文 我和宋清朗相戀三年歧匈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垒酬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖勘究,靈堂內(nèi)的尸體忽然破棺而出矮湘,到底是詐尸還是另有隱情,我是刑警寧澤口糕,帶...
    沈念sama閱讀 34,276評(píng)論 4 329
  • 正文 年R本政府宣布缅阳,位于F島的核電站,受9級(jí)特大地震影響景描,放射性物質(zhì)發(fā)生泄漏十办。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評(píng)論 3 312
  • 文/蒙蒙 一超棺、第九天 我趴在偏房一處隱蔽的房頂上張望向族。 院中可真熱鬧,春花似錦棠绘、人聲如沸件相。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)夜矗。三九已至,卻和暖如春候引,著一層夾襖步出監(jiān)牢的瞬間侯养,已是汗流浹背敦跌。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工澄干, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人柠傍。 一個(gè)月前我還...
    沈念sama閱讀 46,315評(píng)論 2 360
  • 正文 我出身青樓麸俘,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親惧笛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子从媚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容