代码随想录-动态规划-01背包问题(Java)

01背包基础知识

(参考卡哥内容)

01背包问题:

有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值是value[i]。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
两种解法:
二维数组和一位滚动数组

二维dp数组

动态规划五部曲分析:
1.确定dp数组以及下标的含义
dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
要时刻记着dp数组的含义,下面的一些步骤都围绕这dp数组的含义进行的
2.递推公式

  • 不放物品i:由dp[i - 1][j]推出,即背包容量为j,里面不放物品i的最大价值,此时dp[i][j]就是dp[i - 1][j]。(其实就是当物品i的重量大于背包j的重量时,物品i无法放进背包中,所以被背包内的价值依然和前面相同。)
  • 放物品i:由dp[i - 1][j - weight[i]]推出,dp[i - 1][j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的价值),就是背包放物品i得到的最大价值
    所以递归公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
    3.初始化
    dp[i][0] = 0;(默认初始化,不需要进行操作)
    dp[0][j] 在j >= weight[0]的时候,dp[0][j] = value[0];
    4.遍历顺序
    从定义和递推公式出发
    从上到下,从左到右
    5.举例推导:略
    以上推导,i与j的顺序默认 i 遍历的是物品,j遍历的是重量。
    这两者可以颠倒,与此同时,定义初始化等都要跟着相应地变化(一定要注意,思维的统一)

一维滚动dp数组

在使用二维数组的时候,递推公式:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
其实可以发现如果把dp[i - 1]那一层拷贝到dp[i]上,表达式完全可以是:dp[i][j] = max(dp[i][j], dp[i][j - weight[i]] + value[i]);
与其把dp[i - 1]这一层拷贝到dp[i]上,不如只用一个一维数组了,只用dp[j](一维数组,也可以理解是一个滚动数组)。
还是按照动态规划五部曲来进行讲解:
1.dp数组的含义
dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。
2.递推公式
dp[j]可以通过dp[j - weight[i]]推导出来,dp[j - weight[i]]表示容量为j - weight[i]的背包所背的最大价值。

dp[j - weight[i]] + value[i] 表示 容量为 j - 物品i重量 的背包 加上 物品i的价值。(也就是容量为j的背包,放入物品i了之后的价值即:dp[j])

此时dp[j]有两个选择,一个是取自己dp[j] 相当于 二维dp数组中的dp[i-1][j],即不放物品i,一个是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,毕竟是求最大价值,

所以递归公式为:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

3.初始化
dp[0] = 0;
4.遍历顺序(重要)

for (int i = 0; i < wLen; i++){
   for (int j = bagWeight; j >= weight[i]; j--){
            }
        }

内外循环不能变,而且内部循环,重量要采用倒序
【原因:倒序遍历是为了保证物品i只被放入一次!。但如果一旦正序遍历了,那么物品0就会被重复加入多次!】
【再来看看两个嵌套for循环的顺序,代码中是先遍历物品嵌套遍历背包容量,那可不可以先遍历背包容量嵌套遍历物品呢?
不可以!因为一维dp的写法,背包容量一定是要倒序遍历(原因上面已经讲了),如果遍历背包容量放在上一层,那么每个dp[j]就只会放入一个物品,即:背包里只放入了一个物品。】
原因方面:倒序在前,两个嵌套for循环不能颠倒的原因在后。

一、分割等和子集

链接: 416.分割等和子集

思路

本题属于01背包问题的应用

自己有点自大且忽略了很多点

【忽然明白为什么要每天都要做几道算法题了,要保持的是什么思维。隔一天不做算法题,自己只会最直白最简单的思路,所以每天必须要做几道算法题】

分析题意:

给你一个 只包含正整数非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等

分成两份,且元素和相等,不就是 sum / 2

很自然联想到,如果 总和为奇数,则return false

转化为背包问题

背包的容量为sum / 2

每个元素的重量和价值相同均为 nums[i]

每个元素只能取一次

运用一位滚动数组

1.dp数组的含义

dp[j]容量为j的背包中装入的最大和是多少

2.递推公式

直接套入01背包公式

dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);

3.初始化

dp[0] = 0

4.遍历顺序

(二维数组不需要考虑很多,背包容量和物品的遍历顺序也可以颠倒)但是一维滚动数组需要考虑很多东西,不能上来就做

4.1 首先是定义的dp[j]的范围

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

4.2 遍历背包容量和物品的顺序

物品在前面,背包在后面

4.3 j 的范围,从 target开始 倒序 – 到 nums[i]

5.举例推导

代码

class Solution {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int i = 0;i < nums.length;i++){
            sum += nums[i];
        }
        if (sum % 2 == 1)   return false;
        int target = sum / 2;
        int[] dp = new int[target + 1];
        for (int i = 0;i < nums.length;i++){
            for (int j = target;j >= nums[i];j--){
                dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
            }
        }
        return dp[target] == target;
    }
}

二、最后一块石头的重量II

链接: 1049.最后一块石头的重量II

思路

本题尽可能让石头分成重量相同的两堆,这样相撞之后剩下的石头最小

与分割等和子集类似,都可以转化为01背包问题

区别在于分割等和子集,如果数组之和为奇数,则是错误的

而本题即使是奇数,我们可以继续进行操作

一堆石头的重量是:dp[target] target = sum / 2

【不是target,而是dp[target],target是我们计算的数值,dp[target]才是容量为target的容器可以装的最大重量】

另一堆石头的重量是:sum - dp[target]

sum - dp[target] >= dp[target]

所以最后结果是sum - 2 * dp[target]

注意:有时候背包问题的dp[j]不一定和题目直接相关

代码

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int i = 0;i < stones.length;i++){
            sum += stones[i];
        }
        int target = sum / 2;
        int[] dp = new int[target + 1];
        for (int i = 0;i < stones.length;i++){
            for (int j = target;j >= stones[i];j--){
                dp[j] = Math.max(dp[j],dp[j - stones[i]] + stones[i]);
            }
        }
        return sum - 2 * dp[target];
    }
}

三、目标和

链接: 494.目标和

思路

感觉本题其实是小学难度的问题

但是换成算法自己就迷糊了

本题 left 为正数之和,right 为负数之和(不加负号)

left + right = sum

left - right = target

从而求出left = (sum + target) / 2

如果无法整除则return 0

注意如果sum < abs(target) 直接return 0

接着就转化为正常的01背包问题

本题有些不同的是求种类,这与我们爬楼梯问题有些类似

得到的递推公式是:

dp[j] += dp[j - nums[i]];

种类相加 像爬楼梯中dp[j - 1] + dp[j - 2]

代码

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        // 存在负数的情况
        int sum = 0;
        for (int i = 0;i < nums.length;i++){
            sum += nums[i];
        }
        if ( sum < Math.abs(target)) return 0;
        if ((sum + target) % 2 == 1)    return 0;
        // 背包容量为(sum + target) / 2
        int size = (sum + target) / 2;
        if (size < 0)   size = -size;
        int[] dp = new int[size + 1];
        dp[0] = 1;
        for (int i = 0;i < nums.length;i++){
            for (int j = size;j >= nums[i];j--){
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[size];
    }
}

四、一和零

链接: 474.一和零

思路

本题思路其实比较简单,只是代码结构看起来有些让人畏惧

本题因为有 0 和 1,所以是两个维度,设成二维数组

遍历字符串数组,求出字符串数组中各元素中含有多少0和多少个1

不必遍历一遍字符串后在进行其他操作,可以边遍历边进行背包处理

for (int i = m; i >= x;i--){
                for (int j = n;j >= y;j--){
                    dp[i][j] = Math.max(dp[i][j],dp[i - x][j - y] + 1);
                }
            }

i,j在这里都是背包容量的存在,因此要倒序

递推公式由题目可以看出是求数量

dp[i][j] = Math.max(dp[i][j],dp[i - x][j - y] + 1);

有脑筋急转弯的成分。

代码

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp = new int[m + 1][n + 1];
        for (String str : strs){
            int x = 0;
            int y = 0;
            for (int i = 0;i < str.length();i++){
                if (str.charAt(i) == '0')   x++;
                if (str.charAt(i) == '1')   y++;
            }
            // 背包容量
            for (int i = m; i >= x;i--){
                for (int j = n;j >= y;j--){
                    dp[i][j] = Math.max(dp[i][j],dp[i - x][j - y] + 1);
                }
            }
        }
        return dp[m][n];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 《代码随想录知识星球精华-大厂面试八股文v1.1.pdf》是一份介绍八股文的面试指南,针对大厂面试常见题型进行分类,以及给出相应的解答思路。这份资料具有很高的参考价值,在为即将面试的求职者提供指引的同时,也可以让读者对八股文的思维框架和策略有更好的了解。 这份资料主要包括面试题目的主题分类,如动态规划、字符串、树等,每个分类下又相应给出了典型的问题,如“最长公共子序列”、“二叉树的层次遍历”等等。对于每个问题,都给出了具体的思路指导和代码模板,许多题目给出了多种不同的解法,这些解法包括时间复杂度和空间复杂度的优化。 这份资料对于求职者来说,意义重大。在面试中,对于某些问题我们可能没有完全解决,但如果有了这份资料,我们在面试中也会有一个清晰的思考框架和即时可用的代码模板。此外,这份资料同样适用于对算法和数据结构感兴趣的开发者和工程师,对于自学和提高都有帮助。 总之,《代码随想录知识星球精华-大厂面试八股文v1.1.pdf》是一个非常实用的参考材料,它是广大求职者和程序员不可或缺的工具,值得一读。 ### 回答2: 《代码随想录知识星球精华-大厂面试八股文v1.1.pdf》是一份由知名博主“代码随想”的知识星球推出的热门资料。该资料主要介绍了大厂面试中常见的八股文,包括但不限于动态规划、双指针、贪心算法、字符串操作等。 通过学习该资料,可以系统地学习和掌握各种常见的算法和数据结构,有助于提高自己的代码能力和面试竞争力。此外,资料还给出了一些实际的面试题目例子,对于准备去大厂面试的人来说,是一份非常实用的资料。 当然,要想真正掌握这些算法和数据结构,需要自己的不断练习和实践。只有在解决实际问题的过程中,才能真正体会到这些算法和数据结构的作用和优越性。 总之,该资料对于想要深入了解算法和数据结构、提高面试竞争力的人来说是一份值得推荐的优秀资料。 ### 回答3: 代码随想录知识星球精华-大厂面试八股文v1.1.pdf是一份关于大厂面试八股文的精华资料,它收集整理了各个大厂面试中常见的八股文题目和解题思路,对于准备求职或者升职的程序员来说是一份非常有价值的资料。 该资料中涵盖了常见的算法、数据结构、操作系统、计算机网络、数据库、设计模式等知识点,每个知识点都有详尽的讲解和相应的面试题目,可以帮助面试者全面了解每个知识点的考察方向和难点。 此外,该资料还提供了八股文的解题思路和解题方法,强调了实战经验和面试技巧,对于提高面试的成功率也是非常有帮助的。 总之,代码随想录知识星球精华-大厂面试八股文v1.1.pdf是一份非常实用的面试资料,它帮助面试者深入了解各个知识点的考察方向和难点,提高了应对面试的能力和成功率,对于准备求职或者升职的程序员来说是一份不可或缺的资料。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值