416. 分割等和子集
思路
背包问题
滚动数组优化
dp[j]=dp[j-nums[i]]||dp[j]
问题转换成容量为总数除以2的背包能不能放满,两种情况,当前的物品放了和当前的没放
这种方式直接将dp数组的值存储为能否放入,即bool值,显然更加优化。
思路代码
func canPartition(nums []int) bool {
sum:=0
for _,v:=range nums{
sum+=v
}
if sum%2!=0{
return false
}
sum=sum/2
dp:=make([]bool,sum+1)
dp[0]=true
for i:=0;i<len(nums);i++{
for j:=sum;j>=nums[i];j--{
dp[j]=dp[j-nums[i]]||dp[j]
}
}
return dp[sum]
}
官方题解
即一个商品如果可以重复多次放入是完全背包,而只能放入一次是01背包,写法还是不一样的。
要明确本题中我们要使用的是01背包,因为元素我们只能用一次。
回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。
那么来一一对应一下本题,看看背包问题如何来解决。
只有确定了如下四点,才能把01背包问题套到本题上来。
背包的体积为sum / 2
背包要放入的商品(集合里的元素)重量为 元素的数值,价值也为元素的数值
背包如果正好装满,说明找到了总和为 sum / 2 的子集。
背包中每一个元素是不可重复放入。
以上分析完,我们就可以套用01背包,来解决这个问题了。
代码
// 分割等和子集 动态规划
// 时间复杂度O(n^2) 空间复杂度O(n)
func canPartition(nums []int) bool {
sum := 0
for _, num := range nums {
sum += num
}
// 如果 nums 的总和为奇数则不可能平分成两个子集
if sum % 2 == 1 {
return false
}
target := sum / 2
dp := make([]int, target + 1)
for _, num := range nums {
for j := target; j >= num; j-- {
if dp[j] < dp[j - num] + num {
dp[j] = dp[j - num] + num
}
}
}
return dp[target] == target
}
困难
问题转换成容量为总数除以2的背包能不能放满,两种情况,当前的物品放了和当前的没放
今日收获
0-1背包问题,思路是判断当前物品放不放入背包中,即0-1的状态。
同时能对2维dp优化,滚动数组模拟上一层dp,因为只需要上一层dp,为了不影响上一层dp的状态,即不将物品重复放入背包,因为是从左上方取值的,所以应该倒叙遍历。