动态规划笔记

背包问题

动态规划之背包问题系列.
背包九讲----整理+例题.
背包问题常见的有三个问法:

  • 求最大价值/最小代价:f[i][v]表示前i个物品中,放入容量为v的背包中最大价值
  • 求是否刚好能凑出:f[i][v]表示前i个物品中,是否刚好能装满容量为v的背包
  • 求方案总数:f[i][v]表示前i个物品中,装满容量为v的背包的方案总数
  • 求最优方案
    518. 零钱兑换 II
    面试题 08.11. 硬币
    这个题解最后有背包列表

完全背包和多重背包的二进制优化是什么?

是否能恰好装满背包 与 不超过背包体积情况下最大价值

恰好装满

416. 分割等和子集 : 是否能够恰好装满
f[i][v]表示前i个物品中,是否能恰好装满体积为v的背包

322. 零钱兑换 : 恰好装满,代价最小

func coinChange(coins []int, amount int) int {
    dp := make([][]int, len(coins)+1)
    for i:=0; i<len(dp); i++ {
        dp[i] = make([]int, amount+1)
    }

    for i:=1; i<=amount; i++ {
        dp[0][i] = 2*amount
    }
    dp[0][0] = 0

    for i:=1; i<=len(coins); i++ {
        for j:=0; j<=amount; j++ {
            dp[i][j] = 2*amount
            for k:=0; j-k*coins[i-1]>=0; k++ {
                dp[i][j] = min(dp[i][j], dp[i-1][j-k*coins[i-1]]+k)
            }
        }
    }

    if amount>0 && dp[len(coins)][amount] >= 2*amount {
        return -1
    }
    return dp[len(coins)][amount]
}
func findTargetSumWays(nums []int, S int) int {
    l, sum := len(nums), 0
    for _, v := range nums {
        sum += v
    }

    if sum < S || sum < -S || (sum+S)%2 != 0 {
        return 0
    }

    m := (sum+S)/2
    dp := make([][]int, l+1)
    for i:=0; i<=l; i++ {
        dp[i] = make([]int, m+1)
    }
    dp[0][0] = 1

    for i:=1; i<=l; i++ {
        for j:=0; j<=m; j++ {
            dp[i][j] += dp[i-1][j]     
            if j-nums[i-1] >= 0 {
                dp[i][j] += dp[i-1][j-nums[i-1]]
            }     
        }
    } 

    return dp[l][m]
}

不大于背包

474. 一和零 : 01背包,二维背包 ,最大价值

func findMaxForm(strs []string, m int, n int) int {
    dp := make([][][]int, len(strs)+1)
    for i:=0; i<len(dp); i++ {
        dp[i] = make([][]int, m+1)
        for j:=0; j<=m; j++ {
            dp[i][j] = make([]int, n+1)
        }
    }

    for i:=1; i<=len(strs); i++ {
        w1, w2, cs := 0, 0, []byte(strs[i-1])
        for _, v := range cs {
            if v == '0' {
                w1++
            } else {
                w2++
            }
        }
        //fmt.Println(w1, w2)
        for j:=0; j<=m; j++ {
            for k:=0; k<=n; k++ {
                dp[i][j][k] = dp[i-1][j][k]
                if w1 <= j && w2 <= k {
                    dp[i][j][k] = max(dp[i][j][k], dp[i-1][j-w1][k-w2]+1)
                }
            }
        }
    }

    return dp[len(strs)][m][n]
}

https://www.acwing.com/problem/content/submission/code_detail/2307208/

8.16 第一章

什么题型可以用dp

  1. 动态规划可以解决三类问题
  • 求最值 (最大、最小)
  • 求方案数
  • 求可行性问题
    dp类问题肯定包含上面三个提问之一,但有上面问题之一不一定就是dp问题
  1. 如果一个数组满足不可交换时(具有方向性),是dp的信号之一(不是绝对的)

题面

198. 打家劫舍

坐标型

  dp[坐标] = 行走到这个坐标的最优值

特点:状态使用dp(坐标),如果是二维的,就用二维坐标表示,如果是一维就用一维坐标表示
状态转移:关心从哪一个坐标跳过来的
dp[i] = max(dp[i-1], max(dp[0]...dp[i-2]) + a[i]))
增加一个状态进行优化,第二维1表示打劫或者0表示不打劫

把决策(打劫或不打劫)记录到状态中去
这就很像买卖股票,买或不买了

dp[i][0] = max(dp[i-1][0], dp[i-1][1])
dp[i][1] = dp[i-1][0] + a[i]

不用往回推了吗,跟0 ~ i-2的状态没有关系了吗
难以理解

前缀型

  dp[i] = 前i个数的最优值

特点: 前缀表示状态,如看前i个数取出的最大和,不关心第i个数取或者不取
dp[i] = max(dp[i-1], dp[i-2]+a[i])

8.16 第二章

剑指 Offer 60. n个骰子的点数

背包型

  1. 0/1背包
    f[i][j]表示前i个物品,j是和为j,是一堆数为和

dp[i][j] 骰i次骰子和为j的概率
影响概率的是骰子的个数、骰子点数的和,这两个都是变数,放到状态里考虑,所以状态是2维

8.16 第三章

32. 最长有效括号

后缀型(和前缀型一样)

dp[i]表示i ~ len-1 中,以i开始的最长有效括号长度

8.19 第四章

85. 最大矩形

直方图最大矩阵
单调栈
每次将栈中比当前数据大的弹出,最后栈中剩下的数据压入一个0来弹出

8.22 第五章

假期
公司给小q放了n天假,小q打算在假期中工作、锻炼、休息,他有个奇怪的习惯,不会连续两天工作或锻炼。
只有公司营业时,才去工作,只有健身房营业时,采取锻炼。给出公司、健身房营业情况,计算出小q至少要休息几天?

一个线索是决策当前去做什么事情,只与前一天的情况有关,跟之后、更前 都没有关系,这种很容易想到是dp
输入:
放假天数 4
公司营业 1 1 0 0
健身营业 0 1 1 0
输出: 2

j : 0 工作 1 锻炼 2 休息
dp[i][j] 表示第i天做j这个事情,最小的休息天数
滚动数组 dp[i%2]

相关题目
买卖股票

8.22 第6章

1049. 最后一块石头的重量 II
给定一组石头,每个石头有一个正数的重量,每轮开始时,选择两个石头进行碰撞。假定两个石头的重量为x, y, x<=y,碰撞结果为

  1. 如果x == y,两个石头消失
  2. x!=y,两个石头消失并生成一个新石头重量为|x-y|
    最后最多剩一个石头结束,求最小的剩余石头重量
    输入:
    石头个数
    石头重量,空格分开

背包型dp与数组中数据顺序无关、与方向无关
本题可以转为01背包
背包问题要再看下 01背包、完全背包、混合背包

8.22 第7章

1000. 合并石头的最低成本 类似
312. 戳气球
有n堆金币排成一排,第i堆有c[i]块金币。每次合并都将相邻的两堆金币合并为一堆,成本为两堆金币之和。最终合并为一堆,请计算合并为一堆的最低成本。

区间型动态规划
dp[i][j] 表示第i堆到第j堆合并的最小代价
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + sum[i][j])
sum[i][j]表示i到j的和,可以用前缀和来实现

大区间依赖小区间,注意两层循环实现问题
研究最后一步要做啥 反向推,自顶向下的分治方法

更多题目:
877. 石子游戏

grandyang的讲解
leetcode上关于先手后手的讲解
leetcode官方题解
486. 预测赢家

8.22 第8章

字节 毕业旅行 traveling-salesman-problem
状态压缩dp

8.22 第9章

美团:外卖满减
n个菜,价格为prices[i],要选到x元,至少话费多少元
应用反选法,
01背包能不能凑出某个和

这里涉及到选数求和的问题,一般是背包型动态规划。

8.22 第10章

256. 粉刷房子
坐标型

8.22 第11章

最大子数组差 maximum-subarray-difference
给一个整数数组,找出两个不重叠子数组a和b,使两个子数组和的差的绝对值最大 |sum(a) - sum(b)|

相似题目 lintcode 1833 钢笔盒 1850 捡苹果

8.22 第12章

求方案总数、可行性 99%就是dp,求最大最小还不一定
双色塔 two-colors-tower

取模运算
(a + b) / c = (a %c + b %c ) %c
加 减 乘 都满足

区间不重叠,考虑隔板法

posted @ 2020-08-16 12:48  holidays  阅读(175)  评论(0编辑  收藏  举报