● 1049. 最后一块石头的重量 II
● 494. 目标和
● 474.一和零
详细布置
1. 最后一块石头的重量 II
关联 leetcode 1049. 最后一块石头的重量 II
-
思路
- 把石头分成重量近似相等的两堆
- 01背包问题
- 不一定非要有 重量 和 价值两个维度
- 本题就是 重量 同时是 价值
-
dp数组以及下标的含义
dp[j]表示 背包总容量(所能装的总重量)是j时,最大重量为dp[j]
-
递推公式
// 01背包通用公式 // weightJ: J的重量; valueJ: J的价值 dp[j] = max( dp[j], dp[j - weightJ] + valueJ )// max( 不放i , 放入i后 ) // 本题 weightJ := stone[i] valueJ := stone[i] dp[j] = max( dp[j], dp[j - weightJ] + valueJ )
-
dp数组如何初始化
dp[0] = 0 // 非零下标: 初始化 0, 不可能为负数
-
遍历顺序
- 物品从第一个开始
- 背包倒序遍历
- 物品从第一个开始
-
打印数组
-
题解
func lastStoneWeightII(stones []int) int { count := len(stones) sum := 0 for _, stone := range stones { sum += stone } target := sum / 2 // 凑目标值, 总价值的一半 dp := make([]int, target+1) //利用数组零值特性,完成初始化 for i := 0; i < count; i++ { //第一层遍历物品 weightI := stones[i] //当前物品的重量, 背包剩余容量要扣除的重量 valueI := stones[i] //当前物品的价值 for j := target; j >= weightI; j-- { //第二层遍历背包, 背包容量大于石头重量才有价值 //能进入这个循环的背包容量都是大于物品的重量的 dp[j] = max(dp[j], dp[j-weightI]+valueI) // 背包的价值来源于装石头 } } //res:= dp[target] - (sum - dp[target]) // 两个数值之差, 取绝对值 return sum - 2*dp[target] } func max(a, b int) int { if a > b { return a } return b }
2. 目标和
关联 leetcode 494. 目标和
大家重点理解 递推公式:dp[j] += dp[j - nums[i]],这个公式后面的提问 我们还会用到。
视频讲解:动态规划之背包问题,装满背包有多少种方法?| LeetCode:494.目标和_哔哩哔哩_bilibili
https://programmercarl.com/0494.目标和.html
-
思路
- 分出两个集合
-
加法元素集合: left
-
减法元素集合: right
left - right = target left + right = sum left = (sum + target) / 2 // 正数元素集合的值总和 right = (sum - target) / 2 // 负数元素集合的值总和 // 找出所有能组成 正数/负数 元素集合值总和的组合方式
-
- 动规五部曲
-
dp数组以及下标的含义
dp[j] 装满背包容量为 j , 有 dp[j] 种方法
-
递推公式
weightI=nums[i] // 第 i 个物品的重量 // 当前 物品重量 是 weightI 只需要凑到 相差的重量即可 // 方案唯一 一种 dp[j] = dp[j-wightI] dp[5] = dp[4]+dp[3]+dp[2]+dp[1]+dp[0] /* 有点像跳楼梯 */ dp[j] += dp[j-nums[i]] dp[j] += dp[j-weightI]
-
dp数组如何初始化
dp[0] = 1 // 装满背包容量为 0 的背包 , 有 1 种方法 // 其他非零下标初始化为0即可
-
遍历顺序
- 物品从第一个开始
- 背包倒序遍历
- 物品从第一个开始
-
打印数组
-
- 分出两个集合
-
题解
func findTargetSumWays(nums []int, target int) int { sum := 0 for _, num := range nums { sum += num } if sum < abs(target) { //全部取+或取-, 都不可能达到 return 0 // 无解 } if (sum+target)%2 != 0 { // 凑出来的正数集合sum一定是正整数 return 0 //找不到集合 } bagSize := (sum + target) / 2 dp := make([]int, bagSize+1) dp[0] = 1 //取容量为0开始时一定有一种解法 === 什么都不拿 for i := 0; i < len(nums); i++ { for j := bagSize; j >= nums[i]; j-- { dp[j] += dp[j-nums[i]] } } return dp[bagSize] } func abs(a int) int { return int(math.Abs(float64(a))) }
3. 一和零
关联 leetcode 474.一和零
-
思路
- 动规五部曲
-
dp数组以及下标的含义
dp[i][j] : 装满 i 个 0, j 个 1, 最多装了 dp[i][j] 个物品
-
递推公式
// x: 当前元素含有 0 的个数; y: 当前元素含有 1 的个数 dp[i][j] = max( dp[i][j], dp[i-x][j-y]+1 )
-
dp数组如何初始化
dp[0][0] = 0 // 非零下标也初始化成 0 即可
-
遍历顺序
- 先物品后背包, 背包倒序
-
打印数组
-
- 动规五部曲
-
题解
func findMaxForm(strs []string, m int, n int) int { // init the dp array dp := make([][]int, m+1) for i := range dp { dp[i] = make([]int, n+1) } for _, str := range strs { x, y := getCharNum(str, '0'), getCharNum(str, '1') // range backpack // i,j 可交换顺序不影响结果 for i := m; i >= x; i-- { for j := n; j >= y; j-- { dp[i][j] = max(dp[i][j], dp[i-x][j-y]+1) } } } return dp[m][n] } func getCharNum(str string, char rune) int { res := 0 for _, c := range str { if c == char { res++ } } return res }