LeetCode--(动态规划)0-1背包问题

问题分析

有一个容量为N的背包,要用这个背包装下物品的价值最大,这些物品有两个属性:体积w和价值v。定义一个二维数组dp存储最大价值,其中dp[i][j]表示前i件物品体积不超过j的情况下能达到的最大价值。设第i个物品体积为w,价值为v,根据第i件物品是否添加到背包中,可以分两种情况讨论:

  • i件物品没添加到背包中,总体积不超过j的前i件物品的最大价值就是总体积不超过j的前i-1件物品的最大价值,dp[i][j]=dp[i-1][j]
  • i件物品添加到背包中,dp[i][j]=dp[i-1][j-w]+v

i件物品可添加也可以不添加,取决于哪种情况下最大价值更大,因此,0-1背包的状态转移方程为:

dp[i][j]=max(dp[i-1][j],dp[i-1][j-w]+v)

0-1背包问题不能使用贪心算法来解决,也就是说不能按照先添加性价比最高的物品来达到最优,这是因为这种方式可能会造成背包空间的浪费,从而无法达到最优。

416. 分割等和子集(还需再看)

题目大意

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

解题思路

该问题可以看做一个背包大小为sum/2的0-1背包问题。
0-1背包问题的特点是:每个数只能用一次。解决的基本思路是:物品一个一个选,容量也一点一点增加去考虑。
dp[i][j]表示从数组区间[0,i]内挑选一些正整数,每个数只用一次,使得这些数的和为j
状态转移方程:

  • 不选nums[i],如果在[0,i-1]这个子区间内已经有一部分元素,使得他们的和为j,那么dp[i][j]=true;
  • nums[i],如果在[0,i-1]这个子区间内就得找到一部分元素,使得他们的和为j-nums[i].
    所以状态转移方程为:
dp[i][j]=dp[i-1][j] or dp[i-1][j-nums[i]]

进行空间优化,使用一维数组来表示:
每次求解dp[i][v]只用到上一行i-1的值:dp[i-1][v]和dp[i-1][v-w[i]]。考虑用dp[v]来表示之前二维的时候dp[i][v]对应的状态。
i次循环开始之前:dp[j]=dp[i-1][j]
i次循环开始之后:dp[j]=dp[i][j]
那么在第i次循环时,两种选择变为下面的情况:

  • 如果放弃物品i,即dp[i][v]=dp[i-1][v]
  • 如果放入物品i,即dp[i][v]=dp[i-1][v-w[i]],此时,当前的dp[v-w[i]]就是dp[i-1][v-w[i]]
    为了保证先计算dp[i][j]再计算dp[i][j-w],在程序实现时需要按照倒序来循环求解。

代码实现

class Solution {
    public boolean canPartition(int[] nums) {
        //dp[i][j]表示前i个元素是否存在和为j的情况
        int n=nums.length;
        int sum=computeArraysum(nums);
        if(sum%2!=0){
            return false;
        }
        //表示总和
        int W=sum/2;
        //定义状态,行表示数组索引,列表示总和
        boolean dp[][]=new boolean[n][W+1];
        if(nums[0]<=W){
            dp[0][nums[0]]=true;
        }
        for(int i=1;i<n;i++){
            for(int j=0;j<=W;j++){
                dp[i][j]=dp[i-1][j];
                if(nums[i]==j){
                    dp[i][j]=true;
                    continue;
                }
                if(nums[i]<j){
                    dp[i][j]=dp[i-1][j]||dp[i-1][j-nums[i]];
                }
            }
        }
        return dp[n-1][W];
    }
    public int computeArraysum(int[] nums){
        int sum=0;
        for(int num:nums){
            sum+=num;
        }
        return sum;
    }
}

空间优化之后:

class Solution {
    public boolean canPartition(int[] nums) {
        //dp[i][j]表示前i个元素是否存在和为j的情况
        int n=nums.length;
        int sum=computeArraysum(nums);
        if(sum%2!=0){
            return false;
        }
        //表示总和
        int W=sum/2;
        boolean[] dp=new boolean[W+1];
        dp[0]=true;
        for(int num:nums){
            for(int i=W;i>=num;i--){
                dp[i]=dp[i]||dp[i-num];
            }
        }
        return dp[W];
    }
    public int computeArraysum(int[] nums){
        int sum=0;
        for(int num:nums){
            sum+=num;
        }
        return sum;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值