zoukankan      html  css  js  c++  java
  • Medium | LeetCode 416. 分割等和子集 | 0-1背包问题

    416. 分割等和子集

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

    注意:

    1. 每个数组中的元素不会超过 100
    2. 数组的大小不会超过 200

    示例 1:

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

    示例 2:

    输入: [1, 2, 3, 5]
    
    输出: false
    
    解释: 数组不能分割成两个元素和相等的子集.
    

    方法一: 暴力搜索

    枚举所有的子集, 判断其子集的所有和是否是target/2。

    方法二: 0-1 背包问题

    问题可转化为在若干个物品中选出一些物品, 每个物品只能使用一次, 这些物品恰好能够填满容量为sum/2的背包。

    经典的0-1背包问题: 0-1背包问题:在M件物品取出若干件放在体积为W的背包里,每件物品只有一件,它们有各自的体积 和价值,问如何选择使得背包能够装下的物品的价值最多。

    动态规划的思路:一个一个物品去尝试,一点一点扩大考虑能够容纳的容积的大小,整个过程就像是在填写一张二维表格。

    -设置状态:dp[i] [j]表示考虑下标[0, i]这个区间里的所有整数,在它们当中是否能够选出一些数,使得这些数之和恰好为整数j

    -状态转移方程:

    1. 不选择 nums[i]: dp[i] [j] = dp[i - 1] [j];

    2. 选择 nums [i]:
      ① nums [i] == j, dp[i] [j] = true;
      ② nums[i] < j, dp[i] [j] = dp[i - 1] [j - nums [i]];

    递归

    根据以上的状态转移方程, 可以写出如下的递归的代码

    public boolean canPartition(int[] nums) {
        int target = 0;
        for(int num: nums) {
            target += num;
        }
        if (target % 2 == 0) {
            target /= 2;
        } else {
            return false;
        }
        return knapsack(nums, target) == 1;
    }
    
    private int[][] dp;
    
    public int knapsack(int[] value, int w) {
        dp = new int[value.length+1][w+1];
        return knapsackDp(value, w, value.length - 1);
    }
    
    /**
     * @param value  每个物品的价值
     * @param w      包的最大承重
     * @param index  在前index个物品中选择物品装进背包中
     * @return 装载前index个物品进w承重的包的最大价值
     */
    private int knapsackDp(int[] value, int w, int index) {
        // 递归出口(前0件商品, 也就是第一件商品装进背包承重为w的最大价值)
        if (w < 0) {
            return -1;
        }
        if (index == 0) {
            return (w == 0) ?  1 : -1;
        }
        if (dp[index][w] != 0) {
            return dp[index][w];
        }
        return dp[index][w] = Math.max(
                // 把第index件物品装进背包, 即等价于于【把前index-1件物品装进重量为w - weight[index]的背包能获得的最大价值 + 当前index的价值】
                knapsackDp(value, w - value[index], index - 1),
                // 第index件物品不装进背包, 即等价于【把前index-1件物品装进重量为w的背包能获得的最大价值】
                knapsackDp(value, w, index - 1)
        );
    }
    

    迭代

    public boolean canPartition(int[] nums) {
        int n = nums.length;
        if (n < 2) {
            return false;
        }
        int sum = 0, maxNum = 0;
        // 先对所有数字求和
        for (int num : nums) {
            sum += num;
            maxNum = Math.max(maxNum, num);
        }
        // 判断奇偶性, 如果是奇数, 直接返回false
        if (sum % 2 != 0) {
            return false;
        }
        // 获取得到target的值
        int target = sum / 2;
        if (maxNum > target) {
            return false;
        }
        
        boolean[][] dp = new boolean[n][target + 1];
        // 填充第0列的值, 全部填充为true
        for (int i = 0; i < n; i++) {
            dp[i][0] = true;
        }
        // 填充第0行值
        dp[0][nums[0]] = true;
        
        for (int i = 1; i < n; i++) {
            // 从第1行开始从左往右, 从上往下开始遍历
            int num = nums[i];
            for (int j = 1; j <= target; j++) {
                if (j >= num) {
                    // 当前背包容量 大于 当前的体积, 
                    // 那么可有 不把当前物品装进背包dp[i - 1][j]
                    // 把当前物品装进背包两种选择
                    dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
                } else {
                    // 当前背包容量小于物品体积, 那么只能选择将当前物品不装进背包
                    dp[i][j] = dp[i - 1][j];
                }
            }
        }
        return dp[n - 1][target];
    }
    

    优化空间复杂度

    第二层的循环我们需要从大到小计算,因为如果我们从小到大更新dp 值,那么在计算 dp[j] 值的时候,dp[j−nums[i]] 已经是被更新过的状态,不再是上一行的dp 值。

    public boolean canPartition(int[] nums) {
        int n = nums.length;
        if (n < 2) {
            return false;
        }
        int sum = 0, maxNum = 0;
        for (int num : nums) {
            sum += num;
            maxNum = Math.max(maxNum, num);
        }
        if (sum % 2 != 0) {
            return false;
        }
        int target = sum / 2;
        if (maxNum > target) {
            return false;
        }
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            for (int j = target; j >= num; --j) {
                dp[j] |= dp[j - num];
            }
        }
        return dp[target];
    }
    

  • 相关阅读:
    Android sqlite日期存储
    Ubuntu10.04中间Leach协议一键安装
    Chapter 1 Securing Your Server and Network(1):选择SQL Server业务经理
    lua迭代器和仿制药for
    设定值范围+区间覆盖
    新手可以学习cocos2dx 3.0 组态(两)
    王立平--string.Empty
    Javascript操作阵列
    Dojo仪表板
    时间复杂度
  • 原文地址:https://www.cnblogs.com/chenrj97/p/14330293.html
Copyright © 2011-2022 走看看