代码随想录 day42 第九章 动态规划part04

●  01背包问题,你该了解这些!

●  01背包问题,你该了解这些! 滚动数组

●  416. 分割等和子集

理论基础

  • 01背包
    • 每种物品只有一个,只能用一次
  • 完全背包
    • 每种物品有无数个,可以能用无数次

01背包问题 二维

卡码网第46题

  • 背包是可以暴力解决的

    • 取或者不取当前物品 + 回溯实现
      • 时间复杂度 O(2^n)
  • 思路

    1. dp数组以及下标的含义

      //二维数组背包
      dp[i][j] : 从下标为[0-i]的物品里面任意取,放进容量为j的背包,价值总和最大是多少
      
    2. 递推公式

      dp[i][j]
      不放物品i:dp[i-1][j]
      放物品i:dp[i-1][j-weight[i]] + value[i]
      dp[i][j] = max( dp[i-1][j], dp[i-1][ j - weight[i]] + value[i] )
      
    3. dp数组如何初始化

      // 当前元素的上方,和左上方推出来的
      // 第一行一定要初始化,第一列要初始化
      第一列:容量是0,价值全为0
      第一行:容量大于物品重量,值就是第一个物品的价值
      
    4. 遍历顺序

      二维数组:先背包或先物品 for 无所谓
      
    5. 打印数组

  • 题解

    func main() {
    	weight := []int{1, 3, 4}   //物品重量
    	value := []int{15, 20, 30} //物品价值
    	test2DBagProblem(weight, value, 4)
    }
    
    // 二维数组解决 01背包 自带参数版本
    // two dimensional array
    // params:
    //
    //	weight 物品重量
    //	value 物品价值
    //	bagCapacity 背包容量
    func test2DBagProblem(weight, value []int, bagCapacity int) int {
    	totalNums := len(weight) //物品总数量
    
    	// define the
    	dp := make([][]int, totalNums)
    	for i := range dp {
    		dp[i] = make([]int, bagCapacity+1)
    	}
    
    	// init array
    	//init first column
    	for i := 0; i < totalNums; i++ {
    		dp[i][0] = 0
    	}
    	//init first line
    	for j := bagCapacity; j >= weight[0]; j++ {
    		dp[0][j] = dp[0][j-weight[0]] + value[0]
    	}
    
    	// 递推公式, 先物品后背包
    	for i := 0; i < totalNums; i++ {
    		for j := 0; j <= bagCapacity; j++ {
    			if j < weight[i] {
    				dp[i][j] = dp[i-1][j]
    			} else {
    				dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]]+value[i])
    			}
    		}
    	}
    
    	return dp[totalNums-1][bagCapacity]
    }
    
    func max(a, b int) int {
    	if a > b {
    		return a
    	}
    	return b
    }
    
    

01背包问题 一维

卡码网第46题

  • 类似上一题,只是说用每行都用上一行来覆盖计算,实现降维效果

    • 变成滚动数组
      • 需要满足的条件是上一层可以重复利用,直接拷贝到当前层
  • 思路

    1. dp数组以及下标的含义

      dp[j] :表示容量为j的背包,所背物品价值最大为dp[j]
      
    2. 递推公式

      dp[j] = max(dp[j],dp[j-weight[i]]+value[i])
      
    3. dp数组如何初始化

      dp[0] = 0
      // 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了
      
    4. 遍历顺序

      1. 一维数组必须有序,数据会循环使用,要避免数据污染
      2. 先正序遍历物品,再倒序遍历背包
        1. 正序遍历背包,物品存在重复计算
    5. 打印数组

  • 题解

    func main() {
    	weight := []int{1, 3, 4}
    	value := []int{15, 20, 30}
    	test1DBagProblem(weight, value, 4)
    }
    
    func test1DBagProblem(weight, value []int, bagCapacity int) int {
    	dp := make([]int, bagCapacity+1)
    
    	for i := 0; i < len(weight); i++ {
    		// 这里必须倒序,区别二维,因为二维dp保存了i的状态
    		for j := bagCapacity; j >= weight[i]; j-- {
    			dp[j] = max(dp[j], dp[j-weight[i]]+value[i])
    		}
    	}
    	return dp[bagCapacity]
    }
    
    func max(a, b int) int {
    	if a > b {
    		return a
    	}
    	return b
    }
    

416. 分割等和子集

关联 leetcode 416. 分割等和子集

本题是 01背包的应用类题目

  • 思路

    1. dp数组以及下标的含义

      dp[j]表示 背包总容量(所能装的总重量)是j,放进物品后,背的最大重量为dp[j]
      
    2. 递推公式

      dp[j] = max( dp[j], dp[j - nums[i]] + nums[i])
      
    3. dp数组如何初始化

      1. dp[0]一定是0
      2. 如果题目给的价值都是正整数那么非0下标都初始化为0就可以了,如果题目给的价值有负数,那么非0下标就要初始化为负无穷。
      3. 这样才能让dp数组在递推的过程中取得最大的价值,而不是被初始值覆盖了
    4. 遍历顺序

      1. 一维数组
        1. 先物品后背包,背包倒序
    5. 打印数组

  • 题解

    func canPartition(nums []int) bool {
    	sum := 0
    	for _, num := range nums {
    		sum += num
    	}
    	//总和不是偶数, 则一定不能拆除两个元素相等的子集,直接退出
    	if sum%2 != 0 {
    		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 {//省略 max() 比较
    				dp[j] = dp[j-num] + num
    			}
    		}
    	}
    	return dp[target] == target
    }
    

总结

  • 01背包可用 一维 或 二维 背包解决
    • 一维背包需要注意遍历顺序防止重复计算

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值