leetcode 416. 分割等和子集(背包问题)

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

注意:

每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].
 

示例 2:

输入: [1, 2, 3, 5]

输出: false

解释: 数组不能分割成两个元素和相等的子集.

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

本题

典型的背包问题,在n个物品中选出一定物品,填满sum/2的背包
F(n,c)考虑将n个物品填满容量为C的背包
F(i,c) = F(i-1,c) || F(i-1, c-w(i))

记忆化搜索

//记忆化搜索
//memo[i][c] 表示使用索引为[0...i]的这些元素,是否可以完全填充一个容量为c的背包
Integer[][] memo;
public boolean canPartition(int[] nums) {
    int sum = 0;
    for (int i = 0;i<nums.length;i++) sum+=nums[i];

    if (sum%2!=0)   return false;

    memo =  new Integer[nums.length][sum/2+1];
    return tryPartition(nums, nums.length-1, sum/2);
}
//使用nums[0...index],是否可以完全填充一个容量为sum的背包
private boolean tryPartition(final int[] nums, int index, int sum){
    if (sum==0) return true;
    if (sum<0 || index<0)   return false;
    if (memo[index][sum] != null){
        return memo[index][sum] == 1;
    }
    memo[index][sum] =  (tryPartition(nums, index-1, sum) ||
            tryPartition(nums, index-1, sum-nums[index])) ? 1:0;
    return memo[index][sum]==1;
}

动态规划

public boolean canPartition(int[] nums) {
    int sum = 0;
    for (int i = 0;i<nums.length;i++) sum+=nums[i];

    if (sum%2!=0)   return false;

    int n = nums.length;
    int C = sum/2;
    boolean[] memo = new boolean[C+1];
    Arrays.fill(memo, false);

    for (int i = 0;i<=C;i++)
        memo[i] = (nums[0] == i);	//这里初始化表示需要满才为true

    for (int i = 1;i<n;i++)
        for (int j = C;j>=nums[i];j--){
            memo[j] = memo[j] || memo[j-nums[i]];
        }
    return memo[C];
}

背包问题补充

递归

//用 [0...index] 的物品,填充容积为c的背包的最大价值, w[] 为物品所占容量, v[]为物品价值
private int bestValue(int[] w, int[] v, int index, int c){
    if (index<0 || c<=0)    return 0;

    int res = bestValue(w, v, index-1, c);
    if (c>=w[index])
        res = Math.max(res, v[index] + bestValue(w, v, index-1, c-w[index]));
    return res;
}
private int knapsack01(int[] w, int[] v, int c){
    int n = w.length;
    return bestValue(w, v, n-1, c);
}

记忆化搜索

//用 [0...index] 的物品,填充容积为c的背包的最大价值, w[] 为物品所占容量, v[]为物品价值
Integer[][] memo;
 private int bestValue(int[] w, int[] v, int index, int c){
     if (index<0 || c<=0)    return 0;

     if (memo[index][c]!=null)
         return memo[index][c];
     int res = bestValue(w, v, index-1, c);
     if (c>=w[index])
         res = Math.max(res, v[index] + bestValue(w, v, index-1, c-w[index]));
     memo[index][c]=res;
     return res;
 }
 private int knapsack01(int[] w, int[] v, int c){
     int n = w.length;
     memo = new Integer[n][c+1];
     return bestValue(w, v, n-1, c);
 }

0-1背包

1. 未优化空间

时间复杂度:O(nC)
空间复杂度:O(n
C)

private int knapsack01(int[] w, int[] v, int c){
   	int n = w.length;
    int[][] memo = new int[n][c+1];

	//此处是先计算的放入第一件物品的情况(也就是下标为0),然后再遍历1~n-1
	//也可以初始化下标为0的情况,也就是背包为空的情况,然后遍历1~n
	//关于初始化问题 背包九讲中有说明
    for (int j = 0;j<=c;j++)
        memo[0][j] = (j>=w[0] ? v[0] : 0);

    for (int i = 1;i<n;i++){
        for (int j = 0;j<=c;j++){
            memo[i][j] = memo[i-1][j];
            if (j>=w[i])
                memo[i][j] = Math.max(memo[i][j], v[i]+memo[i-1][j-w[i]]);
        }
    }
    return memo[n-1][c];
}

2. 优化空间为O(2*C)

时间复杂度:O(nC)
空间复杂度:O(2
C)

//01背包优化1 空间
private int knapsack01(int[] w, int[] v, int c){
    int n = w.length;
    int[][] memo = new int[2][c+1];

    for (int j = 0;j<=c;j++)
        memo[0][j] = (j>=w[0] ? v[0] : 0);

    for (int i = 1;i<n;i++){
        for (int j = 0;j<=c;j++){
            memo[i%2][j] = memo[(i-1)%2][j];
            if (j>=w[i])
                memo[i%2][j] = Math.max(memo[i%2][j], v[i]+memo[(i-1)%2][j-w[i]]);
        }
    }
    return memo[(n-1)%2][c];
}

3. 优化空间为O©

由于每次更新只参考上边和左边的内容,所以可以从右向左来刷新这一行的内容。
在这里插入图片描述

//01背包优化2 空间
private int knapsack01(int[] w, int[] v, int c){
    int n = w.length;
    int[] memo = new int[c+1];

    for (int j = 0;j<=c;j++)
        memo[j] = (j>=w[0] ? v[0] : 0);

    for (int i = 1;i<n;i++){
        for (int j = c;j>=w[i];j--){
            memo[j] = memo[j];
            if (j>=w[i])
                memo[j] = Math.max(memo[j], v[i]+memo[j-w[i]]);
        }
    }
    return memo[c];
}

0-1背包问题更多变种

完全背包问题:每个物品可以无限使用。
多重背包问题:每个物品不止1个,有num(i)个。
多维费用背包问题:要考虑物品的体积和重量两个维度?

更加复杂:物品键假如更多约束(物品间可以互相排斥;也可以互相依赖)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值