LeetCode0416.分割等和子集 Go语言AC笔记

时间复杂度:O(n²)

解题思路

既然要分割等和,那么就可以看做是选一些元素凑成所有元素总和的一半,而对于每个元素都有选与不选两种状态,所以该题就可以用01背包解题。

用dp[i][j]表示前i个元素能否凑成和为j的值,这样数组中所有不小于j的元素都有两种情况,一种是被选择求和,另外一种就是不被选择被忽略掉。对于前者,dp[i][j]=dp[i-1][j-nums[i]],因为如果nums[i]被选择,那么它的状态就等同于前i-1个元素能否凑成j-nums[i]的和,如果能那么选择i也能凑出j的和,如果不能那肯定凑不出j的和;对于后者,dp[i][j]=dp[i-1][j],因为不被选择,那我们就看前i-1个元素能不能凑出总和j。最后对于这两种情况,如果有一种情况为true,那么dp[i][j]就为true。

所以状态转移方程为

dp[i][j]=dp[i-1][j]||dp[i-1][j-nums[i]],j≥nums[i]

那如果j小于nums[i]呢?肯定是不选择nums[i]呀,如果加上去不就超出我们要的和j了嘛,所以

dp[i][j]=dp[i-1][j],j<nums[i]

那么边界条件是什么呢?由于j表示目标值,所以j为0时,任何元素都可以凑出该值,也就是说,无论i为多少,只要j为0那么dp[i][j]就为true。接下来再考虑i,如果i为0表示不选择任何一个元素,所以除了0值无法凑出任何数。综上,边界条件就是

①dp[i][0]=true,i=0,1,2...  ②dp[0][j]=false,j=1,2,...

仔细观察我们可以发现,i都只依赖于i-1,所以我们可以将二维数组压缩成一维数组,忽略掉i的存在只考虑j,这样我们就可以从大到小遍历更新一维数组,注意必须是从大到小,因为这样不会覆盖掉旧值。这种方法就是滚动数组。

此外我们还需要关注一下特殊情况,一是数组中的最大元素超过了总和的一半,那么无论如何分割数组都会失败,所以该情况必须返回false;二是数组和为奇数,由于元素都是整数,如果和为奇数除以2后为小数也无法分割,故也返回false;三是数组只有一个元素,该情况可以归到第一种情况中。

AC代码

func canPartition(nums []int) bool {
    sum,max:=0,0
    for _,num:=range nums{
        sum+=num
        if num>max{
            max=num
        }
    }
    //数组元素和为奇数无法分割
    if sum&1==1{
        return false
    }
    target:=sum>>1
    //最大元素超过和的一半值,无法分割
    if max>target{
        return false
    }
    dp:=make([]bool,target+1)//dp[i]表示可以用数组中的元素凑出i
    dp[0]=true//边界
    for _,num:=range nums{
        //01背包一定要从大到小遍历
        for j:=target;j>=num;j--{//注意j一定要不小于元素值
            dp[j]=dp[j]||dp[j-num]
        }
    }
    return dp[target]
}

感悟

一开始想复杂了,看了题解才反应过来就是很普通的01背包变种,还是刷题数少了。

二刷感悟

很顺利地想到了01背包,但还是想不到dp[i][j]的含义和状态转移方程,还是得多背多总结。之后对一刷代码进行了优化,把只有1个元素的情况归到了最大元素值超过数组和一半的情况,还把奇偶判断和除以2改成了位运算,提升了时间效率。

扩展:01背包问题

问题描述

有n件物品,每件物品的重量为w[i],价值为c[i]。现有一个容量为V的背包,问如何选取物品放入背包,使得背包内物品的总价值最大。其中每种物品都只有1件。

解题思路

令dp[i][v]表示前i件物品(1≤i≤n,0≤v≤V)恰好装入容量为v的背包中所能获得的最大价值。

对第i件物品的选择有两种策略:

  1. 不放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v的背包中所能获得的最大价值,也即dp[i-1][v]。
  2. 放第i件物品,那么问题转化为前i-1件物品恰好装入容量为v-w[i]的背包中所能获得的最大价值,也即dp[i-1][v-w[i]]+c[i]。

因此状态转移方程为:

dp[i][v]=max{dp[i-1][v],dp[i-1][v-w[i]]+c[i]}(1≤i≤n,w[i]≤v≤V)

而由于dp[i][v]表示的是恰好为v的情况,所以需要枚举dp[n][v](0≤v≤V),取其最大值才是最后的结果。

由于状态转移方程中计算dp[i][v]时总是只需要dp[i-1][v]左侧部分的数据,且当计算dp[i+1][]的部分时,dp[i-1]的数据又完全用不到了(只需要用到dp[i][]),因此不妨可以直接开一个一维数组dp[v](即把第一维省去),枚举方向改变为i从1到n,v从V到0(逆序!),这样状态转移方程变为

dp[v]=max(dp[v],dp[v-w[i]]+c[i])(1≤i≤n,w[i]≤v≤V)

这种技巧成为滚动数组。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SwithunH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值