Day 42 | 01背包-二维数组 & 01背包-一维数组优化 & 416. 分割等和子集

昨天,看了一天,让我真的狠狠受挫了。。昨天只看懂了二维数组,今天早上勉强?看懂了一维数组。。不能放弃!!加油!!

01背包-二维数组 

                                                                         (举例)

动态规划解题思路:

①确定dp数组以及下标含义

       dp[i][j]:从[0,i]号物品随意取,在容量为j的背包下能产生的最大价值。

        注意:i下标从0开始,物品编号为[0,n-1],j下标从0开始,容量编号为[0,size]。

        定义为dp[n][size+1]

②确定递推公式

      先遍历物品,再遍历容量(反过来也可以)

      每次遍历时,首先进行判断当前j容量是否能装下i物品

①当前j值代表容量能装下weight[i],即j>weight[i],此时可以选择装或者不装。

        若,则dp[i][j]=dp[i-1][j-weight[i]]+value[i]  (j容量减去物品i容量值所能装的最大价值+i物品自身的价值);

        若不装,则dp[i][j]=dp[i-1][j] (容量为j,物品从0-i1任取的最大容量)。

        在这两者之前取大值即可。

②j<weight[i],即当前容量装不下物品i,则只能选择装[0,i-1]个物品

        dp[i][j]=dp[i-1][j]

因此,递推公式为:

                                 dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])

 

③dp数组如何初始化

        当j=0时,容量为0,什么也装不下dp[i][0]=0.

        当i=0时,j>weight[i]才能装,因此给大于i容量的dp[0][j]赋值为value[0]即可,其余为初始值0.

        for (int j = weight[0]; j <= bagweight; j++) {
            dp[0][j] = value[0];
        }

④确定遍历顺序

        因为i=0已经被遍历过了,i从1开始遍历即可,i<物品(物品编号从0开始,最后一个物品下标为n-1)。

        j=0时默认值0,j也从1开始遍历,j的编号为[0-size]。

        先遍历背包或者先遍历物品都可以。

        dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i]),dp[i][j]取决于上一行(i-1行)和左上行,所需要的数值不管是先哪个都是已知数,因此从前向后遍历即可,先遍历哪个都可以。

这里不是很理解,遍历顺序为啥先遍历哪个都可以。

⑤举例推导dp数组

    例子中的dp数组如图:

    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagweight = 4;
        int wlen = weight.length, value0 = 0;

        int[][] dp = new int[weight.length][bagweight + 1];
        // 初始化
        for (int j = weight[0]; j <= bagweight; j++) {
            dp[0][j] = value[0];
        }

        // weight数组的大小 就是物品个数
        for (int i = 1; i < weight.length; i++) { // 遍历物品
            for (int j = 1; j <= bagweight; j++) { // 遍历背包容量
                if (j < weight[i]) dp[i][j] = dp[i - 1][j];
                else dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
            }
        }
        for (int i = 0; i < weight.length; i++) {
            for (int j = 0; j <= bagweight; j++) {
                System.out.print(dp[i][j] + " ");
            }
            System.out.print("\n");
        }
    }

01背包-一维数组优化

        从递推公式dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-weight[i]]+value[i])

可以发现,dp[i][j]只取决于上一层的值:

        如果把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]覆盖掉原先的值。(一维数组,也可以理解是一个滚动数组)。

①确定dp数组以及下标含义

     dp[j]表示:容量为j的背包,所背的物品价值可以最大为dp[j]。

②确定递推公式

        去掉i,但是还是两层for循环遍历ij,不过不需要i,因为后面会覆盖掉前面的值,

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

 ③dp数组如何初始化

        dp数组在推导的时候一定是取价值最大的数,如果题目给的价值都是正整数那么非0下标都初始化为0就可以了。

        这样才能让dp数组在递归公式的过程中取的最大的价值,而不是被初始值覆盖了

        那么我假设物品价值都是大于0的,所以dp数组初始化的时候,都初始为0就可以了。

④确定遍历顺序

       遍历容量j时,从最大容量向前遍历,遍历到weight[i]停止。

        因为如果从前向后遍历,dp[i-1][j]前面的值已经被更新成dp[i][j]了,那么dp[j]就有可能把一个物品放进去两次。倒序遍历是为了保证物品i只被放入一次!

        右下角的值依赖上一层左上角的值,因此需要保证左边的值仍然是上一层的,从右向左覆盖。

这里我画表格大概理解了,但是具体还不是很通透

      先遍历物品,再遍历容量(反过来不可以)

        个人理解为如果先遍历了容量,只知道dp上方的值,但是左上方的值还未确定,因此不能这样遍历。如果先遍历了容量,只知道dp上方的值,但是左上方的值还未确定,因此不能这样遍历。

⑤举例推导dp数组 

    public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagweight = 4;
        int wlen = weight.length, value0 = 0;
        int[] dp = new int[bagweight + 1];
        for (int i = 0; i < weight.length; i++) {
            for (int j = bagweight; j >= weight[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }

        for (int i = 0; i < dp.length; i++) {
            System.out.println(dp[i]);
        }

    }

416. 分割等和子集

 这道题就是我不看题解完全套不到背包问题上+看了题解也不是很理解为啥这样就可以了 

 转化为背包问题解题思路:

        本题可以看成有一个sum/2容量的背包,有n个物品,每个物品的重量和价值都为nums[i],问是否有数能组合成sum的一半。若容量用完j==target时,最大价值dp[j]恰好等于容量target,即dp[target]==target。则符合题意。

    public static boolean canPartition(int[] nums) {
        int sum = 0;
        for (int i : nums) {
            sum += 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]);
            }

        }
        if (dp[target] == target) {
            return true;
        }
        return false;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值