这是我参与2022首次更文挑战的第23天,活动详情查看:2022首次更文挑战
分割等和子集
算法一:动态规划(Java)
这道题可以换一种表述:给定一个只包含正整数的非空数组 nums[0],判断是否可以从数组中选出一些数字,使得这些数字的和等于整个数组的元素和的一半。因此这个问题可以转换成「0-10−1 背包问题」。这道题与传统的「0-10−1 背包问题」的区别在于,传统的「0-10−1 背包问题」要求选取的物品的重量之和不能超过背包的总容量,这道题则要求选取的数字的和恰好等于整个数组的元素和的一半。类似于传统的「0-10−1 背包问题」,可以使用动态规划求解。
- 先遍历物品,然后遍历背包重量
- weight数组的大小 就是物品个数
for(int i = 1; i < weight.size(); i++) { // 遍历物品
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
- 先遍历背包,再遍历物品
- weight数组的大小 就是物品个数
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
for(int i = 1; i < weight.size(); i++) { // 遍历物品
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
-
每次递归,都有两个选择:
- 选nums[i]。基于选它,往下继续选(递归):dfs(curSum + nums[i], i + 1)
- 不选nums[i]。基于不选它,往下继续选(递归):dfs(curSum, i + 1)
-
递归的终止条件有三种情况:
- curSum > target,已经爆了,不用继续选数字了,终止递归,返回false。
- curSum == target,满足条件,不用继续选数字了,终止递归,返回true。
- 指针越界,考察完所有元素,能走到这步说明始终没有返回true,所以返回false。
class Solution {
public boolean canPartition(int[] nums) {
if (nums == null || nums.length == 0) {
return false;
}
int sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
}
if (sum % 2 != 0) {
return false;
}
int target = sum / 2;
boolean[] dp = new boolean[target + 1];
for (int i = 0; i < nums.length; i++) {
for (int j = target; nums[i] <= j; j--) {
if (dp[target]) {
return true;
}
if (nums[i] == j) {
dp[j] = true;
continue;
}
dp[j] = dp[j] || dp[j - nums[i]];
}
}
return dp[target];
}
}
时间复杂度:O(N * target)
空间复杂度:O(target)
算法一:动态规划(Go)
思路同上
func canPartition(nums []int) bool {
sum := 0
for _, n := range nums {
sum += n
}
if sum%2 == 1 {
return false
}
target := sum / 2
var dfs func(curSum, i int) bool
dfs = func(curSum, i int) bool {
if i == len(nums) || curSum > target {
return false
}
if curSum == target {
return true
}
return dfs(curSum+nums[i], i+1) || dfs(curSum, i+1)
}
return dfs(0, 0)
}
时间复杂度:O(N * target)
空间复杂度:O(target)