题目链接:416. 分割等和子集 - 力扣(LeetCode)
前情提要:
因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。
最近刚学完01背包,所以现在的题解都是以01背包问题为基础再来写的。
如果大家不懂01背包的话,建议可以去学一学,01背包问题可以说是背包问题的基础。
如果大家感兴趣,我后期可以出一篇专门讲解01背包问题。
dp五部曲。
1.确定dp数组和i下标的含义。
2.确定递推公式。
3.dp初始化。
4.确定dp的遍历顺序。
5.如果没有ac打印dp数组 利于debug。
每一个dp题目如果都用这五步分析清楚,那么这道题就能解出来了。
这里下文统一使用一维dp数组。
题目思路:
题目还是比较好入手,判断数组是否可以分为俩个自己,并使俩个子集的元素和相等。
由此可见,如果一个数组他的元素和为偶数的话,就可以使俩个子集的元素和相等。
如果为奇数,就不可能使俩个子集元素和相等,直接return false。
这只是一个小的优化。我们来看看核心思路。
分成俩个子集后,如果我们确定了一个子集的元素和为整个数组和的一半,另一半的就可以确定下来了。
所以我们分析其中的一个子集就可以。
如果一个子集的元素和等于整个数组的元素和的一半,那么这个子集就可以确定下来,整个数组就可以分为俩个子集。
怎么确定个子集的元素和等于整个数组的元素和的一半呢?
这里就要用到01背包的应用了。
01背包是有n个物品,每个物品有其对应的价值和重量,给你一个容量为m的背包,让你求该背包所能放下物品的最大价值。
那么这道题的子集怎么跟01背包关联呢?
其实这个子集的元素和就可以当做01背包的背包容量,元素本身可以当做他的价值和重量,即他的价值和重量都一致。
怎么理解呢?
我们要判断一个集合里是否有数能累加起来等于整个数组元素和的一半。
元素和当做背包容量。
元素本身就是物品。
元素的值就是物品的价值,同时也代表它所占的背包容量。
其实01背包我们是要尽可能的将背包装满然后得出他的最大价值。
那这个子集的元素和就是我们尽可能的将背包装满判断他的最大价值等于容量即可。
接着我们用dp五部曲来系统分析。
1.确定dp数组和i下标的含义。
dp[i] 表示i容量的背包所能放下的最大价值。
如果后面有不理解的,多理解一下dp数组的含义。
2.确定递推公式。
我们每个元素只有选和不选俩种状态。
即选择nums[i]这个元素时,对于背包只有选和不选俩个状态。
我们类比一下01背包问题的递推公式dp[j] = Math.max(dp[j],dp[j - weight[i] + value[i]);
没选的状态就是dp[j]。没有选他那他的重量肯定就不变。
选的状态就是dp[j - weigtht[i]] + value[i]。
既然选择要加nums[i],那我们肯定要求出在放入他之前的最大价值再加上他自身的价值就是背包整个的价值。
放入之前的容量就是j - weigth[i]
因为本题是重量与价值一致。
我们尽可能的将背包填满使它的价值等于它背包的容量。如果等于则return true反之return false
所以当背包容量为j时,dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
3.dp初始化。
初始化也很重要,当我们的元素和为0时,那我们所占的元素价值为多少?
肯定为0啊,所以dp[0] = 0。
那其他非零元素和我们怎么初始化呢?
其实非零元素和我们可以不管他们,他们都可以根据当前元素选或不选来推出元素和的价值。
4.确定dp的遍历顺序。
背包问题我们的遍历顺序是先遍历物品再遍历背包,同理,元素和也是先遍历物品(元素)再遍历背包(元素和)。
且第一层循环遍历物品是从前往后遍历,而第二层循环是从后往遍历。
从后往前遍历背包容量是确保每个物品只放一次,即不需要前面的状态。
如果从前往后遍历背包容量可能导致一个物品放入多次,他使用了前面的状态。
而本题元素只能使用一次,所以背包容量应该从后往前遍历。
5.如果没有ac打印dp数组 利于debug。
最后dp[i]模拟后情况就是这样,大家可以打印dp数组对着看看哪与你的不符。
最终代码:
class Solution {
public boolean canPartition(int[] nums) {
//定义元素和
int sum = 0;
for(int i = 0;i < nums.length; i++){
sum += nums[i];
}
//判断如果不为偶数 直接返回false
if(sum % 2 != 0)return false;
//目标元素和
int tagert = sum / 2;
//定义dp数组
int[] dp = new int [tagert + 1];
//初始化dp数组
dp[0] = 0;
//确定dp数组的遍历顺序
for(int i = 0;i < nums.length;i ++){
for(int j = tagert;j >= nums[i];j --){
dp[j] = Math.max(dp[j],dp[j - nums[i]] + nums[i]);
//如果背包价值等于背包容量 直接return true
if(dp[j] == tagert){
return true;
}
}
}
return false;
}
}
背包问题就是这样,思路分析一大堆,实际代码一小堆。
我们多做多理解就好。
这一篇博客就到这了,如果你有什么疑问和想法可以打在评论区,或者私信我。
我很乐意为你解答。那么我们下篇再见!