背包问题(一)--01背包

参考资料
背包九讲
https://www.acwing.com/activity/content/11/

01背包模型问题

 背包容量为V,有N件物品,每件物品的体积是vi,价值是wi,每件物品最多使用一次。求将哪些物品装入背包,得到的价值最大
思路
利用子问题定义状态进行求解。f(i,v)表示取前i件物品恰好放入容量小于等于v的背包可以获得的最大价值,则
f(i,v) = max( f( i-1 , v ) , f( i-1,v-vi ) + wi )
f(i,v)状态可以转化为只考虑第i件物品:
 如果选择第i件物品,那么此时的价值就是f(i-1,v-vi)+wi,f(i-1,v-vi)即将前i-1件物品放入容量小于等于v-vi的背包的最大价值
 如果不选择第i件物品,那么此时的价值就是f(i-1,v),即将前i-1件物品放入容量小于等于v的背包的最大价值
 只需要选择两者中最大的那个就是f(i,v)的状态了
 最终答案就是f(N,V),即取前N件物品放入容量小于等于V的背包中的最大价值
代码实现

    public static void main(String[] args){
        // 读取数据
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int V = sc.nextInt();
        int[] v = new int[N+1];
        int[] w = new int[N+1];
        for (int i = 0; i < N; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }
        // 01背包
        int[][] dp = new int[N+1][V+1];
        // dp[0][0...V] 默认为0
        for (int i = 1; i <= N; i++) {
            for (int j = 0; j <= V; j++) {
                dp[i][j] = dp[i-1][j];
                if (j >= v[i]) {
                    dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v[i]]+w[i]);
                }
            }
        }
        System.out.println(dp[N][V]);
    }

记录最大价值时选中的商品

// 如果需要记录物品是否被选中,可以使用一个二维数组记录物品状态,path[i][j]
// f(i,v) = max( f( i-1 , v ) , f( i-1,v-vi ) + wi )
// 如果f( i-1 , v)比较大,path[i][v] = 0
// 如果f( i-1 , v - vi ) + wi比较大,path[i][v] = 1
// 然后从path[N][V]倒推出选中的商品
    public static void main(String[] args) {
        // 读取数据
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt();
        int V = sc.nextInt();
        int[] v = new int[N + 1];
        int[] w = new int[N + 1];
        for (int i = 0; i < N; i++) {
            v[i] = sc.nextInt();
            w[i] = sc.nextInt();
        }
        // 01背包
        int[][] dp = new int[N + 1][V + 1];
        int[][] path = new int[N + 1][V + 1];
        // dp[0][0...V] 默认为0
        for (int i = 1; i <= N; i++) {
            for (int j = 0; j <= V; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= v[i] && dp[i - 1][j - v[i]] + w[i] > dp[i][j]) {
                    dp[i][j] = dp[i - 1][j - v[i]] + w[i];
                    // 物品被选中,path[i][j] = 1
                    path[i][j] = 1;
                }
            }
        }
        // 打印被选中物品的下标
        for (int i = N, j = V; i > 0 && j > 0; i--) {
            if (path[i][j] == 1) {
                System.out.println(i+"号物品被选中");
                j -= v[i];
            }
        }
    }

空间复杂度优化(优化部分其实和具体题目关系不大了,完全是代码的优化)
; 时间复杂度O(V*N)以及确定了。空间复杂度,之前使用的是二维数组,可以降维到一维数组
 转移方程:f(i,v) = max( f( i-1 , v ) , f( i-1,v-vi ) + wi ),可以观察到f(i,v)只和上一行有关
 每一次循环,我们总是用新的一行去记录当前最大价值,实际上我们用到的只是上一行的值,那完全可以用dp[2][V]去代替之前的dp[N][V]。(即滚动数组)
 如果用一维数组能否实现记录每次循环的最大价值呢,即dp[i][j]行记录的内容由dp[j]得到

image.png

看之前的代码

 for (int i = 1; i <= N; i++) {
            for (int j = 0; j <= V; j++) {
                dp[i][j] = dp[i-1][j]; // 使用一维数组后,这行可以去掉,当前遍历,数组中存的就是上一行的数据
                if (j >= v[i]) { // 当j < v[i]的时候,条件不成立,for循环可以改成j从v[i]开始,j -> v[i] ... V
                    dp[i][j] = Math.max(dp[i][j],dp[i-1][j-v[i]]+w[i]); // dp[i][j]依赖于dp[i-1][j-v[i]],如果j从0...V的话,j
                                                                        // 后面的值可能会得到dp[i][j-v[i]],因此j要从V...0
                                                                                              
                }
            }
        }

转移方程:dp[j] = max(dp[j],dp[j-vi]+w[j])

 for (int i = 1; i <= N; i++) {
            for (int j = V; j >=v[i]; j--) {
                if (j >= v[i]) {
                    dp[j] = Math.max( dp[j],dp[ j - v[i] ] + w[i] );
                }
            }
 }

leetcode题目

leetcode416题 分割等和子集

题目描述:
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200

示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
 
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

求解思路:
将数组分为两个子集,即选择一些数字组成一个集合,未选择的就是另一个集合
dp[i][j] 表示前i个数中选出一些,和正好等于j。dp[i][j]为boolean类型
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
代码实现:


       public boolean canPartition(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int N = nums.length;
        int sum = 0;
        for (int i : nums) {
            sum += i;
        }
        if (sum % 2 != 0) {
            return false;
        }
        int target = sum / 2;
        boolean[][] dp = new boolean[N][target + 1];
        if (nums[0] <= target) {
            dp[0][nums[0]] = true;
        }
        for (int i = 1; i < N; i++) {
            for (int j = 0; j <= target; j++) {
                dp[i][j] = dp[i - 1][j];
                if (nums[i] <= j) {
                    dp[i][j] = dp[i][j] || dp[i - 1][j - nums[i]];
                }
            }
        }
        return dp[N - 1][target];
    }

空间优化

    public boolean canPartition(int[] nums) {
        if (nums == null || nums.length == 0) {
            return false;
        }
        int N = nums.length;
        int sum = 0;
        for (int i : nums) {
            sum += i;
        }
        if (sum % 2 != 0) {
            return false;
        }
        int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        if (nums[0] <= target) {
            dp[nums[0]] = true;
        }
        for (int i = 1; i < N; i++) {
            for (int j = target; j >= nums[i]; j--) {
                dp[j] = dp[j] || dp[j - nums[i]];
            }
        }
        return dp[target];
    }

leetcode494题 目标和

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个
整数,你都可以从 + 或 -中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例 1:

输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释: 

-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

一共有5种方法让最终目标和为3。
注意:

数组非空,且长度不会超过20。
初始的数组的和不会超过1000。
保证返回的最终结果能被32位整数存下。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/target-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

leetcode1049题 最后一块石头的重量 II

有一堆石头,每块石头的重量都是正整数。

每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:

如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头最小的可能重量。如果没有石头剩下,就返回 0。

示例:
输入:[2,7,4,1,8,1]
输出:1
解释:
组合 2 和 4,得到 2,所以数组转化为 [2,7,1,8,1],
组合 7 和 8,得到 1,所以数组转化为 [2,1,1,1],
组合 2 和 1,得到 1,所以数组转化为 [1,1,1],
组合 1 和 1,得到 0,所以数组转化为 [1],这就是最优值。
 
提示:
1 <= stones.length <= 30
1 <= stones[i] <= 1000

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/last-stone-weight-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容