优化0-1背包问题

public static int max_back() {        // 0-1 背包优化,使用一维数组来实现
        int[] weight = new int[]{2, 2, 6, 5, 4};
        int[] value = new int[]{6, 3, 5, 4, 6};

        int Max = 10;   // 最大容量
        int n = 5;      // 物品件数

        int[] dp = new int[Max+1];
        dp[0] = 0;
        for(int i = 0; i < n; i++) {
            for(int j = Max; j >= weight[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
            }
        }
        return dp[Max];
    }

为什么for循环里面的第二个循环要从后往前遍历? 这是因为如果从前往后遍历的话,后面再次判断的时候就会将前面已经求出的值添加进去。  可能不太好理解,这里举个例子,就从 i = 0 的时候这个例子,如果 j 从weight[i] 开始往后走的话,首先dp[j] 是肯定会被赋值 value[i] 的,这时,随着 j 的值的增加,继续判断,当 j = 2*weight[i] 的时候,注意,这个时候dp[j] 将会被赋值为 两倍的 value[i] (仔细想想) , 然而,这是肯定不符合 0-1 背包的定义的。

 

leetcode:416 分割等和子集   https://leetcode-cn.com/problems/partition-equal-subset-sum/

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

前期思路:求出数组中所有整数之和sum,判断是否能分成两个子集,转化为判断是否存在部分数组元素之和等于sum/2

public boolean canPartition(int[] nums) {
        int len = nums.length;
        if(len <= 1) return false;
        int sum = 0;
        for(int i = 0; i < len; i++) sum += nums[i];
        if(sum%2 == 1) return false;

        sum /= 2;

        int[] dp = new int[sum+1];
        // 转化为 0-1 背包问题,sum此时就代表背包的容量
        // 即判断 是否容量为sum时,能够达到sum !
        // 这里 价值与重量 相等!
        dp[0] = 0;
        for(int i = 0; i < len; i++) {
            for(int j = sum; j >= nums[i]; j--) {
                dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);
            }
        }

        return dp[sum] == sum;
    }

 

将判断数组中是否存在一部分整数之和等于sum/2,转化为0-1背包问题。

一部分整数之和,对应的就是对于一个整数,我们可以选择放到背包中和不放到背包中。

最后,只需要判断,当背包容量为sum/2时,背包中的最大价值(重量?, 这里应该都是一样的) 是否可以达到sum/2

另一种解法:

class Solution {
    public boolean canPartition(int[] nums) {
        int len = nums.length;
        if(len <= 1) return false;
        int sum = 0;
        for(int i = 0; i < len; i++) sum += nums[i];
        if(sum%2 == 1) return false;
        sum /= 2;
        
        // 动态规划,0-1背包,求使得nums和为sum的
        boolean[] dp = new boolean[sum+1];
        // dp[i] 代表是否能够凑成大小为 i 的数
        dp[0] = true;
        
        for(int i = 0; i < len; i++) {
            for(int j = sum; j >= nums[i]; j--) {
                dp[j] = dp[j] || dp[j-nums[i]];
                // j == nums[i]时,dp[0] = true, 必能凑成
            }
        }
        return dp[sum];
    }
}

 

----------------更新

leetcode : 494题 目标和

发现做了上面两道题,碰到类似的题目还是有点转不过来。。。

class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        // 动态规划解答
        int sum = 0;
        int len = nums.length;
        if(len == 0) return 0;
        for(int i = 0; i < len; i++) sum += nums[i];
        sum += S;
        if(sum %2 == 1 || 2*S > sum) return 0;

    	sum /= 2;
    	int[] dp = new int[sum+1];
    	// dp[i] 代表能凑成 i 有多少中凑法
    	dp[0] = 1;
    	for(int i = 0; i < len; i++) {
    		for(int j = sum; j >= nums[i]; j--) {
    			dp[j] = dp[j] + dp[j-nums[i]];
    		}
    	}

    	return dp[sum];
    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值