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)。