• 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

    那么边界条件是什么呢?由于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代码

    1. func canPartition(nums []int) bool {
    2. sum,max:=0,0
    3. for _,num:=range nums{
    4. sum+=num
    5. if num>max{
    6. max=num
    7. }
    8. }
    9. //数组元素和为奇数无法分割
    10. if sum&1==1{
    11. return false
    12. }
    13. target:=sum>>1
    14. //最大元素超过和的一半值,无法分割
    15. if max>target{
    16. return false
    17. }
    18. dp:=make([]bool,target+1)//dp[i]表示可以用数组中的元素凑出i
    19. dp[0]=true//边界
    20. for _,num:=range nums{
    21. //01背包一定要从大到小遍历
    22. for j:=target;j>=num;j--{//注意j一定要不小于元素值
    23. dp[j]=dp[j]||dp[j-num]
    24. }
    25. }
    26. return dp[target]
    27. }

    感悟

    一开始想复杂了,看了题解才反应过来就是很普通的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)

    这种技巧成为滚动数组。

  • 相关阅读:
    基于前后端分离的博客系统
    bash -s 的作用
    软件测试工程师简历项目经验怎么写?--1000个已成功入职的软件测试工程师简历范文模板(含真实简历)
    二分查找和冒泡排序
    众佰诚:抖音开网店新手怎么做才能做起来
    Matlab地理信息绘图—研究区域绘制
    HTML5期末大作业:基于HTML+CSS+JavaScript茶文化中国水墨风格绿色茶叶销售(5页) 学生网页设计作业源码
    没想到三天10KStar的营销利器MediaCrawler开源作者已经删库了
    IoC和DI
    SpringCloud入门教程一(微服务原理、Eureka注册中心、Ribbon负载均衡,nacos注册中心)
  • 原文地址:https://blog.csdn.net/Hexa_H/article/details/126939717