welcome to my blog
LeetCode Top 100 Liked Questions 416. Partition Equal Subset Sum (Java版; Medium)
题目描述
Given a non-empty array containing only positive integers, find if the array can be partitioned into two
subsets such that the sum of elements in both subsets is equal.
Note:
Each of the array element will not exceed 100.
The array size will not exceed 200.
Example 1:
Input: [1, 5, 11, 5]
Output: true
Explanation: The array can be partitioned as [1, 5, 5] and [11].
Example 2:
Input: [1, 2, 3, 5]
Output: false
Explanation: The array cannot be partitioned into equal sum subsets.
01背包: 行是物品, 列是容量
class Solution {
public boolean canPartition(int[] nums
) {
int sum
= 0;
for(int a
: nums
){
sum
+= a
;
}
if((sum
&1)==1){
return false;
}
sum
= sum
/2;
boolean[][] dp
= new boolean[nums
.length
][sum
+1];
if(nums
[0]<=sum
){
dp
[0][nums
[0]] = true;
}
for(int i
=0; i
<nums
.length
; i
++){
dp
[i
][0] = true;
}
for(int i
=1; i
<nums
.length
; i
++){
for(int j
=1; j
<sum
+1; j
++){
dp
[i
][j
] = dp
[i
-1][j
];
if(j
-nums
[i
]>=0){
dp
[i
][j
] = dp
[i
][j
] || dp
[i
-1][j
-nums
[i
]];
}
}
}
return dp
[nums
.length
-1][sum
];
}
}
第一次做; 背包问题; 优化二维DP的空间复杂度, 优化成一维; 核心:普通的二维dp中, dp[i][j]只依赖它正上方的元素,和正上方左边的某个元素; 优化后仍是两层for循环, 第二层for循环要从后往前遍历, 因为前面的状态不会依赖后面的状态, 所以改变后面的状态没有影响, 但是绝对不能从前往后遍历; 先把普通的dp掌握牢固
class Solution {
public boolean canPartition(int[] nums
) {
int sum
= 0;
for(int a
: nums
)
sum
+= a
;
if(sum
%2==1)
return false;
int target
= sum
>>1;
boolean[] dp
= new boolean[target
+1];
dp
[0]=true;
for(int j
=1; j
<=target
; j
++){
if(nums
[0]==j
)
dp
[j
]=true;
}
for(int i
=1; i
<nums
.length
; i
++){
for(int j
=target
; j
>=1; j
--){
if(j
>=nums
[i
])
dp
[j
] = dp
[j
] || dp
[j
-nums
[i
]];
}
}
return dp
[target
];
}
}
坚持画图
第一次做; 背包问题; 为什么这道题没法直接从暴利递归转成dp? 为什么这道题没法通过memo[]数组优化暴利递归?
时间复杂度O(N^2)空间复杂度O(N)
import java
.util
.Arrays
;
class Solution {
public boolean canPartition(int[] nums
) {
int sum
= 0;
for(int a
:nums
)
sum
+= a
;
if(sum
%2==1)
return false;
int target
= sum
>>1;
int n
= nums
.length
;
boolean[][] dp
= new boolean[n
][target
+1];
for(boolean[] d
: dp
)
Arrays
.fill(d
, false);
dp
[0][0] = true;
for(int i
=0; i
<n
; i
++)
dp
[i
][0] = true;
for(int j
=1; j
<=target
; j
++){
if(nums
[0] == j
)
dp
[0][j
]=true;
}
for(int i
=1; i
<n
; i
++){
for(int j
=1; j
<=target
; j
++){
dp
[i
][j
] = dp
[i
-1][j
];
if(j
>=nums
[i
])
dp
[i
][j
] = dp
[i
-1][j
] || dp
[i
-1][j
-nums
[i
]];
}
}
return dp
[n
-1][target
];
}
}
第一次做; 对于每个位置:选或者不选; 递归终止条件:1)index==nums.length; 2)currSum*2==sum;递归函数逻辑:选或者不选当前元素,然后新条件新递归; 超时:28 / 105
class Solution {
public boolean canPartition(int[] nums
) {
int sum
= 0;
for(int i
=0; i
<nums
.length
; i
++){
sum
+= nums
[i
];
}
return core(nums
, 0, 0, sum
);
}
public boolean core(int[] nums
, int index
, int currSum
, int sum
){
if(index
==nums
.length
)
return false;
if(currSum
<<1 == sum
)
return true;
boolean res
= false;
res
= core(nums
, index
+1, currSum
, sum
);
if(res
==true)
return true;
res
= core(nums
, index
+1, currSum
+nums
[index
], sum
);
return res
;
}
}
LeetCode最优解
This problem is essentially let us to find whether there are several numbers in a set which are able to sum to a specific
value (in this problem, the value is sum/2).
Actually, this is a 0/1 knapsack problem, for each number, we can pick it or not. Let us assume dp[i][j] means whether the
specific sum j can begotten from the first i numbers. If we can pick such a series of numbers from 0-i whose sum is j,
dp[i][j] is true, otherwise it is false.
Base case: dp[0][0] is true; (zero number consists of sum 0 is true)
Transition function: For each number, if we don't pick it, dp[i][j] = dp[i-1][j], which means if the first i-1 elements
has made it to j, dp[i][j] would also make it to j (we can just ignore nums[i]). If we pick nums[i]. dp[i][j] = dp[i-1][j-nums[i]],
which represents that j is composed of the current value nums[i] and the remaining composed of other previous numbers.
Thus, the transition function is dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
talking is cheap:
public boolean canPartition(int[] nums
) {
int sum
= 0;
for (int num
: nums
) {
sum
+= num
;
}
if ((sum
& 1) == 1) {
return false;
}
sum
/= 2;
int n
= nums
.length
;
boolean[][] dp
= new boolean[n
+1][sum
+1];
for (int i
= 0; i
< dp
.length
; i
++) {
Arrays
.fill(dp
[i
], false);
}
dp
[0][0] = true;
for (int i
= 1; i
< n
+1; i
++) {
dp
[i
][0] = true;
}
for (int j
= 1; j
< sum
+1; j
++) {
dp
[0][j
] = false;
}
for (int i
= 1; i
< n
+1; i
++) {
for (int j
= 1; j
< sum
+1; j
++) {
dp
[i
][j
] = dp
[i
-1][j
];
if (j
>= nums
[i
-1]) {
dp
[i
][j
] = (dp
[i
][j
] || dp
[i
-1][j
-nums
[i
-1]]);
}
}
}
return dp
[n
][sum
];
}
But can we optimize it? It seems that we cannot optimize it in time. But we can optimize in space. We currently use two dimensional array to solve it, but we can only use one dimensional array.
So the code becomes:
public boolean canPartition(int[] nums
) {
int sum
= 0;
for (int num
: nums
) {
sum
+= num
;
}
if ((sum
& 1) == 1) {
return false;
}
sum
/= 2;
int n
= nums
.length
;
boolean[] dp
= new boolean[sum
+1];
Arrays
.fill(dp
, false);
dp
[0] = true;
for (int num
: nums
) {
for (int i
= sum
; i
> 0; i
--) {
if (i
>= num
) {
dp
[i
] = dp
[i
] || dp
[i
-num
];
}
}
}
return dp
[sum
];
}