代码随想录-刷题第四十二天

0-1背包理论基础

0-1背包问题介绍

0-1背包问题:有n件物品和一个最多能背重量为w 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。

代码随想录-刷题第四十二天_第1张图片

0-1背包问题可以使用回溯法进行暴力求解,指数级别的时间复杂度。所以才需要动态规划的解法来进行优化!

举例说明:

背包最大重量为4。

物品为:

重量 价值
物品0 1 15
物品1 3 20
物品2 4 30

问背包能背的物品最大价值是多少?

下面围绕该例子展开动态规划如何求解0-1背包问题。


二维dp数组解决0-1背包问题

依然动规五步曲分析一波。详情见代码随想录


一维dp数组(滚动数组)解决0-1背包问题

对于背包问题其实状态都是可以压缩的。详情见代码随想录


416. 分割等和子集

题目链接:416. 分割等和子集

思路:使用0-1背包问题,只有确定了以下几点,才能够将0-1背包应用到这个问题上来。

  • 背包的体积为sum / 2
  • 背包要放入的商品(集合里的元素)重量为元素的数值,价值也为元素的数值
  • 背包如果正好装满,说明找到了总和为 sum / 2 的子集。
  • 背包中每一个元素是不可重复放入的。

动规五步曲分析如下:

  1. 0-1背包中,dp[j]:容量为j的背包,所背的物品价值最大可以为dp[j]。

    本题中每一个元素的数值既是重量,也是价值。套到本题,dp[j]表示背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]

    那么如果背包容量为target, dp[target]就是装满背包之后的重量,所以当 dp[target] == target 的时候,背包就装满了。

    也还有装不满的时候:拿输入数组 [1, 5, 11, 5],举例, dp[7] 只能等于 6,因为 只能放进 1 和 5。而dp[6] 就可以等于6了,放进1 和 5,那么dp[6] == 6,说明背包装满了。

  2. 0-1背包的递推公式为:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

    本题,相当于背包里放入数值,那么物品i的重量是nums[i],其价值也是nums[i]

    所以递推公式:dp[j] = max(dp[j], dp[j - nums[i]] + nums[i])

  3. 初始化:dp[0] = 0,非0下标也都是0就可以。

    从dp[j]的定义来看,首先dp[0]一定是0。

    如果题目给的价值都是正整数,那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。

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

  4. 遍历顺序:先顺序遍历物品(也就是数组从前往后遍历),背包容量需要倒序遍历。

    如果使用一维dp数组,物品遍历的for循环放在外层,遍历背包的for循环放在内层,且内层for循环倒序遍历!

  5. 举例推导dp数组

    dp[j]的数值一定是小于等于j的。

    如果dp[j] == j 说明,集合中的子集总和正好可以凑成总和j,理解这一点很重要。

    以输入[1,5,11,5] 为例,如图:

    代码随想录-刷题第四十二天_第2张图片

    最后dp[11] == 11,说明可以将这个数组分割成两个子集,使得两个子集的元素和相等。

class Solution {
    public boolean canPartition(int[] nums) {
        // dp[j]:容量为j的背包,所背的物品价值最大可以为dp[j]。
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }
        
		//总和为奇数,不能平分
        if (sum % 2 != 0) return false;
        int target = sum / 2;

        int[] dp = new int[target + 1];
        // 递推公式:dp[j] = max(dp[j], dp[j - weight[i]] + value[i])
        // 初始化:dp[0] = 0,非0下标也可以都是0。
        // 遍历顺序:物品(也就是数组从前到后遍历),背包容量需要倒序遍历
        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;
    }
}

这道题目就是一道0-1背包应用类的题目,需要我们拆解题目,然后套入0-1背包的场景。

0-1背包相对于本题,主要要理解,题目中物品是nums[i],重量是nums[i],价值也是nums[i],背包体积是sum/2。


你可能感兴趣的:(数据结构,数据结构,算法,java,leetcode,动态规划)