传送门
题目:给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
1.每个数组中的元素不会超过 100
2. 数组的大小不会超过 200
示例: 输入: [1, 5, 11, 5] 输出: true
解释: 数组可以分割成 [1, 5, 5] 和 [11].
关于0-1背包图解表格: 这里
0-1背包基础上解答本题: 这里
相当于找到一组数字,使其和为数组和的2分之1
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) sum += num;
if (sum % 2 != 0) return false;
sum /= 2;
boolean[][] dp = new boolean[nums.length][sum + 1];
// 第一行初始化 让i从1开始遍历 因为 方程里包含有dp[i-1][j]
if (nums[0] <= sum) dp[0][nums[0]] = true;
for (int i = 1; i < nums.length; ++i) {
for (int j = 0; j <= sum; ++j) {
if (nums[i] == j) dp[i][j] = true;
else if (nums[i] < j) {
dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];//两个都是i-1!!!
}
}
}
return dp[nums.length - 1][sum];
}
上面每次扫描一行之后,更新下一行;那么只用一个列大小的dp数组,每扫描一行,把下一行的dp[j]拉下来更新这一行。
这题要注意的是: 列的更新要从后往前定义,
因为列的值代表要组成的和,更新它的时候: dp[j] = dp[j] || dp[j - nums[i]]; 要用到j-nums[i]的dp值,显然在表格中,它的dp值在j的dp值的左边(因为j-nums[i]肯定比j小,列从左到右增大);
但是注意的是,这个j-nums[i]的dp值是上一层的,如果dp[j]从左到右更新,在更新dp[j]时,dp[j-nums[i]]的值已经更新完了,那么它取到的dp[j-nums[i]]的值就是这一层的值,不是上一层的啦。
public boolean canPartition(int[] nums) {
int sum = 0;
for (int num : nums) sum += num;
if (sum % 2 != 0) return false;
sum /= 2;
boolean[] dp = new boolean[sum + 1];
// 第一行初始化 让i从1开始遍历 因为 方程里包含有dp[j]
if (nums[0] <= sum) dp[nums[0]] = true;
for (int i = 1; i < nums.length; ++i) {
for (int j = sum; j >= 0; --j) {
// 列遍历方向变了
if (nums[i] == j) dp[j] = true;
else if (nums[i] < j) {
dp[j] = dp[j] || dp[j - nums[i]];
}
}
}
return dp[sum];
}