【算法】01背包问题解析

什么是01背包问题?

        01背包问题的题目描述一般是:给你一个正数m,代表背包的容量。此时有n个物品,每个物品都有自己的体积costs[i]和价值values[i],每个物品只能选择一次。问在不超过背包容量m的情况下,让你尽可能的往背包里放物品,你能让背包装载的最大价值是多少?

如何明确01背包的dp数组定义及状态转移方程?

        (1)解决01背包问题一般用二维dp数组,那么dp[i][j]的含义是:对于前i个物品,背包容量为j的情况下,背包所能装载的最大价值是dp[i][j]。

        (2)对于第i个物品,我们只有两种选择:1.不放入背包,dp[i][j] = dp[i - 1][j],不放入背包不就意味着你的最优解与前i - 1个物品相关嘛; 2.放入背包,dp[i][j] = dp[i - 1][j - costs[i]] + values[i],放入背包前,需要确认一件事:j - costs[i] > 0,背包容量没有到达上限。因为我们选择将第i个物品放入背包,所以我们需要加上它相应的价值values[i]。

        (3)最后在放入背包与不放入背包之间选择一个较大值。

//外层循环代表物品前i个物品,前0个物品无论怎么放,得到的最大价值都是0
//所以循环从1开始。
for(int i = 1;i < n + 1;i ++){

    for(int j = 0;j < m + 1;j ++{
        //选择不放入背包
        dp[i][j] = dp[i - 1][j];
        //背包容量允许第i个物品放入背包时
        if(j - costs[i] > 0){
            //在不放入背包和放入背包中选择一个较大值
        
            dp[i][j] = Math.max(dp[i - 1][j],dp[i - 1][j - costs[i]] + values[i]);
        }
    }
}

题目1:416. 分割等和子集 - 力扣(LeetCode)

题目描述:

问题分析:

        (1)根据题目描述我们可以知道,题目是想让我们找出一个子序列,这个子序列的和等于数组所有元素之和的一半。

        (2)那我们是不是可以把问题转化成:给你一个容量为sum / 2的背包,让你用数组中的元素去填满这个背包,问是否能恰好填满这个背包。

Code:

class Solution {
    public boolean canPartition(int[] nums) {
        int n = nums.length;

        int sum = 0;
        for(int x : nums)
        sum += x;

        //数组总和为奇数时,无论怎么分都无法分出两个值相等的子集
        if(sum % 2 != 0)
        return false;

        sum /= 2;

        //dp[i][j]含义:对于可选择的前i个物品,当背包容量为j时,是否能用这前i个物品恰好填满背包
        boolean [][] dp = new boolean[n + 1][sum + 1];

        //背包容量为0时,前i个物品总能填满背包
        for(int i = 0;i < n + 1;i++)
        dp[i][0] = true;

        for(int i = 1;i < n + 1;i ++){
            for(int j = 1;j < sum + 1;j ++){
                //由于背包容量限制,不允许第i个物品放入
                if(j - nums[i - 1] < 0){
                    dp[i][j] = dp[i - 1][j];
                }else{
                    //背包可以放下第i个物品,只要有一个满足要求即可
                    dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]];
                }
            }
        }
        //dp[n][sum]:对于数组中的n个元素,是否有一种组合能恰好填满背包容量sum
        return dp[n][sum];
    }
}

题目2:1049. 最后一块石头的重量 II - 力扣(LeetCode)

题目描述:

问题分析:

        (1)首先我们对石头数组做出假设,假设它为:[1,7,8],它的最优解是什么?应该是8与7相撞变成1,再跟1相撞变成0,这就是最优解;那石头数组为:[5,6,7,8],它的最优解又是什么?应该是8与6相撞变成2,7与5相撞变成2,再让两个2相撞变成0,这就是最优解。

        (2)根据上述例子与题目1我们可以想到什么?将石头数组进行分组![1,7,8]中,1和7属于集合A,8属于集合B;[5,6,7,8]中,6和7属于集合A,5和8属于集合B。两个集合的差值就是剩下的石头最小的可能重量。

        (3)所以我们要做的就是让数组尽量分成数值接近的两个集合,这两个集合的差值就是我们需要的答案。

Code:

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int n = stones.length;
        int sum = 0;

        for(int stone : stones)
        sum += stone;

        //让集合尽量等分成数组总和的一半
        //这样能让两个集合的差值最小
        sum /= 2;

        //dp[i][j]含义:对于前n个石头,在不超过sum的情况下,所能装载的最大值为dp[i][j]
        int [][] dp = new int[n + 1][sum + 1];

        for(int i = 1;i <= n;i ++){
            for(int j = 0;j <= sum;j ++){
                //第i个物品不放入背包的情况
                dp[i][j] = dp[i - 1][j];
                //如果背包的剩余容量允许第i个物品放入背包
                if(j - stones[i - 1] >= 0){
                    //则在将第i个物品放入背包和不放入背包中取一个较大值
                    dp[i][j] = Math.max(dp[i][j],dp[i - 1][j - stones[i - 1]] + stones[i - 1]);
                }
            }
        }
        int sum2 = 0;

        for(int stone : stones)
        sum2 += stone;

        return sum2 - dp[n][sum] - dp[n][sum];
    }
}

补充说明:

        返回结果为什么是数组总和 - dp[n][sum] - dp[n][sum] ?

        (1)首先dp数组的目的是:在原数组中求一个子序列,这个子序列的和在<=sum的情况下的最大值。以数组[5,5,6,7]为例,数组总和是23,sum向下取整等于11,数组中5与6的和恰好等于11,所以dp[n][sum] = 11.

        (2)已经求出dp[n][sum] = 11了,其中一个集合的值已经确定了,那么另外一个集合的值不就是数组总和 - dp[n][sum]吗?同时,我们之前通过分析知道答案与两个集合的差值有关,那结果不就是:数组总和 - dp[n][sum] - dp[n][sum]嘛。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值