01背包问题

一、问题概述

        01背包问题也是一个经典问题了,题目是这样的:

        给定一组物品,物品有两个属性:价值和重量;还有一个背包Bag,可以最多装下重量为n的物品。问背包能装的最大价值是多少?

        01背包问题和完全背包区别在于:01背包中的物品只有两种状态:取或不取。最简单的办法是什么呢?暴力枚举,举例所有的情况。但是耗时太长了,时间复杂度达到O(2^{n}),在LeetCode中甚至不能ac...

二、问题解法

        看了很久的01背包问题,始终有一些小问题让我不能清晰地理解透01背包。后来终于找到一篇解析:0-1背包问题-简书,推荐还不清楚的hxd去看看。

        关键1:问题的核心在于先解决子问题,再解决最终问题。

        什么意思?设置dp二维数组肯定是这样设置的:dp[i][j],表示的意思是:在前i个物品中,装满j重量的背包所能得到的最大价值。dp[i][j]就是最终解的一个个子问题。

        先看dp[1][j]:表示第一个物品装在j容量的包里得到的最大价值。

        如果weight[1] > j ?那包里肯定是装不下第一个物品的,所有j < weight[1]的格子,dp[1][j]都是0。当第一次出现j > weight[1],dp[1][j]就等于values[1]了。

        所以,得到的dp数组第一行的情况是这样的:(假设weight[1] == 3)

[0,0,0,values[1],values[1],values[1]]

        再看第二个物品加入选项的时候:

        当weight[2] > j的时候,j重量的包里还是放不下第二个物品的,所以dp[2][j]应该等于dp[1][j],因为包里可能放得下第一个物品,在上一次循环中已经计算过了。

        当weight[2] < j的时候,j重量的包里放得下第二个物品,我们可以选择放或不放。如果放第二个物品,那么剩余的重量为j - weight[2],此时包中物品的价值应该为:value[2] + dp[1][j-weight[2]];如果不放第二个物品,包里物品的价值为:dp[1][j]。在二者中取最大值,就是我们想要的结果。

        关键2:如果选择放入该物品,背包中是可能含有子空间的,子空间能获得的最大价值也需要考虑。

三、代码部分

//一组物品,有价值和重量,在背包limit重量的限制下,装满背包的最大价值
    public int maxValue(int[] values,int[] weight,int limit){
        int[][] dp = new int[values.length+1][limit+1];
        //dp[i][j] 表示在0~i的物品里,装满j重量的最大价值
        for(int i = 1; i < values.length; i++){
            for(int j = 1; j <= limit; j++){
                if(weight[i] > limit){
                    //当前物品重量大于limit,不能放置当前物品
                    dp[i][j] = dp[i-1][j];
                }else{
                    //可以放置
                    dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-weight[i]] + values[i]);
                }
            }
        }
        return  dp[values.length][limit];
    }

四、LeetCode相关题目:分割等和子集

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

思路是这样的:求出所有元素的和sum,如果sum是奇数,则一定不能得到子集。得到target,target = sum / 2。问题变成了01背包问题:在nums数组中取出子集,使得子集元素的和为target。

(思路是自己想到的,和题解一样,表扬一下我自己)

代码部分:

public boolean canPartition(int[] nums) {
        if(nums.length < 2) return false;
        //求总和
        int sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum += nums[i];
        }
        //总和为奇数,肯定不能拆分
        if(sum % 2 == 1) return false;
        //求一个子集,和等于target
        int target = sum / 2;
        //取与不取,01问题
        int length = nums.length;
        boolean[][] dp = new boolean[length][target+1];
        //dp[i][j] 的含义是:在0~i中选取元素,能否得到和为j的组合
        for(int i = 0; i < length; i++){
            //和等于0,在[0~i]中都可以达成
            dp[i][0] = true;
        }
        for(int i = 1; i < length; i++){
            for(int j = 1; j <= target; j++){
                if(nums[i] > j){
                    dp[i][j] = dp[i-1][j];
                }else{
                    dp[i][j] = dp[i-1][j] | dp[i-1][j-nums[i]];
                }
            }
        }
        return dp[length-1][target];
    }

PS:我的描述可能不够清晰,只是以我的理解发出来供参考。建议去看0-1背包问题 - 简书

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值