1. question: 分割等和子集(中等)
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/partition-equal-subset-sum
示例 1:
1 2 3
   | 输入:nums = [1,5,11,5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11] 。
   | 
 
示例 2:
1 2 3
   | 输入:nums = [1,2,3,5] 输出:false 解释:数组不能分割成两个元素和相等的子集。
   | 
 
提示:
1 2
   | 1 <= nums.length <= 200 1 <= nums[i] <= 100
   | 
 
2. answers
这道题要求判断给定的数组,能够划分成两个元素和相等的子集。既然是元素和相等,那么肯定要求总元素和为偶数,如果为奇数,肯定是不行的。
之后,可将问题转换为01背包问题,即确定是偶数之后,将其除以二,得到目标值。此时,将问题转换为在给定的元素中,选取子集,判断总和是否能够刚好达到目标值。
类比01背包问题,物品价值value就是给定的元素值,物品权重weight也是自身元素值,背包容量就是 总和/2。注意,此时如果背包刚好装满,也就意味着所装元素的value和就达到了目标值。(因为value和weight是等值的)
此时,问题就成为了:在给定的元素中,背包是否刚好装满。
类比01背包问题,设置二维数组dp[i][j],表示在当前容量j的情况下,所能达到的不超过容量j的最大值,即如果超过了,显然装不下,也就是最多能够装多少。对于最终结果,即dp[n][目标值],也就是在背包为目标值容量下,在给定的n个元素中,所能够达到的最大重量。如果最大容量刚好等于目标值,即能够取得结果,也就是可以将子集划分成两个目标和相等的子集。
二维数组的尺寸为[n, target+1]。注意初始化元素。
至于递推公式,和前面的01背包一样,对于每个物品,只有取和不取两种状态。
- 取的话,肯定要保证容量足够,即回退到剩余容量刚好够的情况下;判断取和不取两种情况,取最大值(因为保证了肯定不会超过 target,因为不会超过容量)。
 
- 不取的话,只有当无论怎么操作,容量都不够时,也就是当前物品的weight大于容量。
 
代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
   | public class Solution_0119 {
      public boolean canPartition(int[] nums) {
                   int sums = 0;         for(int i: nums) sums += i;         if(sums % 2 != 0) return false;
                   int contains = sums / 2;
                            int[][] result = new int[nums.length][contains + 1];                           for (int i = 0; i < result.length; i++) {             result[i][0] = 0;         }
                   for (int i = 0; i < contains + 1; i++) {             if(nums[0] <= i)                 result[0][i] = nums[0];         }
                   for (int i = 1; i < nums.length; i++) {
              for (int j = 1; j < contains + 1; j++) {
                                   if(nums[i] > j) result[i][j] = result[i-1][j];
                                   else result[i][j] = Math.max(result[i-1][j], result[i-1][j-nums[i]] + nums[i]);
                                                                 }         }
          return result[nums.length - 1][contains] == contains;     }
      public static void main(String[] args) {         System.out.println();
 
          int[] nums = {1,2,3,5};
          Solution_0119 s = new Solution_0119();
          System.out.println(s.canPartition(nums));     } }
  | 
 
采用一维动态数组优化后的代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   | public boolean canPartition(int[] nums) {
           int sums = 0;     for(int i: nums) sums += i;     if(sums % 2 != 0) return false;
      int target = sums / 2;
           int[] dp = new int[target + 1];
           for (int i = 0; i < target + 1; i++) {
                   if(nums[0] <= i) dp[i] = nums[0];     }
           for (int i = 1; i < nums.length; i++) {
                   for (int j = target; j > 0; j--) {
                           if(nums[i] > j) dp[j] = dp[j];
                           else dp[j] = Math.max(dp[j], dp[j-nums[i]] + nums[i]);         }
      }
      return dp[target] == target; }
  | 
 
3. 备注
参考力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台 (leetcode-cn.com),代码随想录 (programmercarl.com)。