代码随想录笔记_动态规划
代码随想录二刷笔记记录
LC416.分割等和子集
题目
01背包
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
示例 1:
输入:nums = [1,5,11,5]
输出:true
解释:数组可以分割成 [1, 5, 5] 和 [11] 。
示例 2:
输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。
思路分析
思路:
将数组分割成两个子集,使子集 A 和子集 B 的总和相等
-> 找到集合中出现 sum/2 的子集总和.就可以分割成两个相等子集
加入01背包的概念
1.背包的容量 sum/2
2.背包需要放入的商品重量为元素的数值nums[i],价值也是nums[i]
3.背包如果正好装满,代表找到了相等子集
4.背包中的每一个元素都是不可重复放入
动态规划五部曲
1.确定dp数组及其下标含义
dp[j]:表示能使得两个子集相等的其中一个子集总和.
简单地说就是(符合题意)子集总和
2.确定递推公式
dp[j] = max(dp[j],dp[j-nums[i]] + nums[i])
本题的物品的重量和价值都是 nums[i]
3.初始化
由滚动数组的知识可知,num中的元素都为正整数,则dp初始化为0即可。
只有全部初始化为0,在推导时才能取到最大值。
反之,如果 num 中的元素存在负数,则dp中的非0下标元素需要初始化为负无穷。
4.遍历顺序
for(int i = 0;i < nums.length;i++){//先遍历物品,即数组数值
for(int j = target; j >= nums[i];j --){//遍历背包容量
//注意,每个元素只能被放进一次,因此采用倒序,避免重放
dp[j] = max(dp[j],dp[j - nums[i]] + nums[i]);
}
}
5.推演分析
以 [1,5,5,11] 为例
容量 j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
元素1 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
元素5 | 0 | 1 | 1 | 1 | 1 | 5 | 6 | 6 | 6 | 6 | 6 | 6 |
元素5 | 0 | 1 | 1 | 1 | 1 | 5 | 6 | 6 | 6 | 6 | 6 | 11 |
图示:
代码实现
完整代码实现
public boolean canPartition(int[] nums) {
if (null == nums || nums.length == 0) return false;
// num 求和
int sum = Arrays.stream(nums).sum();
// 如果 sum 为奇数,代表找不到两个相同的子集
if (sum % 2 == 1) return false;
// 背包容量
int bagSize = sum /2;
// 初始化dp数组
int[] dp = new int[bagSize + 1];
for (int i = 0; i < nums.length; i++) { // 遍历元素
for (int j = bagSize; j >= nums[i]; j--) { // 遍历背包
dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
}
}
return bagSize == dp[bagSize];
}