zoukankan      html  css  js  c++  java
  • 【LeetCode-动态规划】分割等和子集

    题目描述

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

    注意:

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

    示例:

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

    题目链接: https://leetcode-cn.com/problems/partition-equal-subset-sum/

    思路

    该问题是 01 背包问题,所谓 01 背包问题,就是将 n 个物品放入容量为 V 的背包,每个物品仅有一件,且有两个属性:体积和价值,每个物品要么放要么不放,求解将哪些物品放入背包能得到最大价值。

    推广到这一题,假设数组中元素的和为 s,则 s 的一半为 target = s / 2,那么问题就变为了我们将数组中的元素放入一个容量为 target 的背包里,数组中的元素代表物品的体积,则我们要判断是否存在一种放法使得背包被恰好放满。

    根据 01 背包问题的解法,我们可以将状态定义为 dp[i][j](类型为 bool 型),表示从区间 [0, i] 中挑选一些数,每个数只用一次,这些数的和是否恰好等于 j。那么对于第 i 个数,存在两种情况:选择和不选择:

    • 选择第 i 个数,则 dp[i][j] = dp[i-1][j-nums[i]],说明如果可以从区间 [0, i-1] 个数中选择若干数,它们的和为 j-nums[i],那么也就存在从 [0, i] 中选择若干数,它们的和为 j;
    • 不选择第 i 个数,则 dp[i][j] = dp[i-1][j],说明如果可以从区间 [0, i-1] 中选择若干数,这若干个数的和为 j,那么可以从区间 [0, i] 选择若干数,使得它们的和为 j(不选第 i 个数即可)。

    这两种情况只要有一种成立,就说明从 [0, i] 中选择若干数,这若干数的和为 j 是成立的。也就是 dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]

    代码如下:

    class Solution {
    public:
        bool canPartition(vector<int>& nums) {
            if(nums.size()<2) return false;
    
            int s = 0;
            for(int i=0; i<nums.size(); i++) s += nums[i];
            if(s%2!=0) return false;  // 不能平分返回 false
    
            int target = s/2;
            vector<vector<bool>> dp(nums.size(), vector<bool>(target+1, false));
            if(nums[0]<=target) dp[0][nums[0]] = true;
    
            for(int i=1; i<nums.size(); i++){  // 第一层循环选物品
                for(int j=0; j<=target; j++){  // 第二层循环将背包容量从0到targe递推
                    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[nums.size()-1][target];
        }
    };
    

    空间复杂度优化:
    01 背包可以进行空间复杂度优化,也就是将二维的 dp 数组改为一维。这样的话,dp[j] 就代表了 dp[i][j]。状态转移方程 dp[i][j] = dp[i-1][j] || dp[i][j-nums[i]] 就变为了 dp[j] = dp[j] || dp[j-nums[i]]。代码如下:

    class Solution {
    public:
        bool canPartition(vector<int>& nums) {
            if(nums.size()<2) return false;
    
            int s = 0;
            for(int i=0; i<nums.size(); i++) s += nums[i];
            if(s%2!=0) return false;  // 不能平分返回 false
    
            int target = s/2;
            vector<int> dp(target+1);
            if(nums[0]<=target) dp[nums[0]] = true;
    
            for(int i=1; i<nums.size(); i++){  // 第一层循环选物品
                for(int j=target; j>=nums[i]; j--){  // 第二层循环将背包容量从0到targe递推
                    if(dp[target]) return true;
                    dp[j] = dp[j] || dp[j-nums[i]];
                }
            }
            return dp[target];
        }
    };
    

    需要注意的是,第二层循环要逆序循环,也就是容量从高到低遍历。

    参考

    https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/

  • 相关阅读:
    JAVA-throw new IOException报错unhandled exception:java.lang.Exception 2021年6月7日
    GIt保持远程 源仓库与Fork仓库同步--2017年6月13日
    Python的getattr()-2017年6月7日
    JavaScript学习-2017年5月18日
    Writing your first Django app--2017年5月9日
    M4-AC6 Oh,Trojan Again--2017年5月9日
    吴军硅谷来信
    【1】Prologue--A Game of Thrones--2017年4月8日
    M4-PC9 Read 10,000 Books,Travel 10,000 Miles--2017年5月8日
    资源分配图RAG的化简
  • 原文地址:https://www.cnblogs.com/flix/p/13573993.html
Copyright © 2011-2022 走看看