题目:
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
注意:
每个数组中的元素不会超过 100
数组的大小不会超过 200
示例 1:
输入: [1, 5, 11, 5]
输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
示例 2:
输入: [1, 2, 3, 5]
输出: false
解释: 数组不能分割成两个元素和相等的子集.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
思路:
「0 - 1」 背包问题的思路
作为「0-1 背包问题」,它的特点是:「每个数只能用一次」。解决的基本思路是:物品一个一个选,容量也一点一点增加去考虑,这一点是「动态规划」的思想,特别重要。
在实际生活中,我们也是这样做的,一个一个地尝试把候选物品放入「背包」,通过比较得出一个物品要不要拿走。
具体做法是:画一个 len 行,target + 1 列的表格。这里 len 是物品的个数,target 是背包的容量。len 行表示一个一个物品考虑,target + 1多出来的那 1 列,表示背包容量从 0 开始考虑。很多时候,我们需要考虑这个容量为 0 的数值。
状态与状态转移方程
状态定义:dp[i][j]表示从数组的 [0, i] 这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于 j。
状态转移方程:很多时候,状态转移方程思考的角度是「分类讨论」,对于「0-1 背包问题」而言就是「当前考虑到的数字选与不选」。
不选择 nums[i],如果在 [0, i - 1] 这个子区间内已经有一部分元素,使得它们的和为 j ,那么 dp[i][j] = true;
选择 nums[i],如果在 [0, i - 1] 这个子区间内就得找到一部分元素,使得它们的和为 j - nums[i]。
状态转移方程:
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]
一般写出状态转移方程以后,就需要考虑初始化条件。
j - nums[i] 作为数组的下标,一定得保证大于等于 0 ,因此 nums[i] <= j;
注意到一种非常特殊的情况:j 恰好等于 nums[i],即单独 nums[j] 这个数恰好等于此时「背包的容积」 j,这也是符合题意的。
因此完整的状态转移方程是:
\text{dp}[i][j]= \begin{cases} \text{dp}[i - 1][j], & 至少是这个答案,如果 \ \text{dp}[i - 1][j] \ 为真,直接计算下一个状态 \\ \text{true}, & \text{nums[i] = j} \\ \text{dp}[i - 1][j - nums[i]]. & \text{nums[i] < j} \end{cases}
dp[i][j]=
⎩
⎪
⎪
⎨
⎪
⎪
⎧
dp[i−1][j],
true,
dp[i−1][j−nums[i]].
至少是这个答案,如果 dp[i−1][j] 为真,直接计算下一个状态
nums[i] = j
nums[i] < j
作者:liweiwei1419
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo/
来源:力扣(LeetCode)
Java代码:
class Solution {
public boolean canPartition(int[] nums){
int len = nums.length;
int sum = Arrays.stream(nums).sum();
if(sum%2 != 0){
return false;
}
int target = sum / 2;
boolean[][] dp = new boolean[len][target+1];
for(int i=1;i<len;i++){
for(int j=1;j<target+1;j++){
dp[i][j] = dp[i-1][j] || nums[i] == j;
if(j>nums[i]){
dp[i][j] = dp[i][j] || dp[i-1][j-nums[i]];
}
}
}
return dp[len-1][target];
}
}