动态规划
Date:
动态规划
动态规划 Dynamic Programming
动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
入门
完整掌握一道动态规划题目,需要在做题前|做题中|AC后思考以下问题:
- DP数组及下标的实际含义,得考虑清楚
- 递推公式
- DP数组如何初始化
- 遍历顺序
- 打印DP数组
基础问题
- 斐波那契数列
- dp[i] 即为 fib(i)
dp[i] = dp[i-1] + dp[i-2]
- 爬楼梯
- dp[i] 为到第 i 层有几种方法
- 条件:每一次可以爬两层或爬一层
dp[i] = dp[i - 1] * 1 + dp[i - 2] * 1
- 最小花费爬楼梯
- dp[i] 为到第 i 层的最小花费
- 条件:每一次可以爬一层或两层
dp[i] = Math.min(dp[i-2] + cost[i-2], dp[i-1] + cost[i- 1])
- 不同路径(从[0,0]到[m,n]的路径数)
- dp[i][j] 为到[i,j]的路径数
- 条件:每一步只能向右走或向下走
if(i>0) dp[i][j] += dp[i-1][j]
if(j>0) dp[i][j] += dp[i][j-1]
不同路径II(在上题基础上新加了障碍物,障碍物dp[i][j]为0即可解决)
- 整数拆分
- 描述:把一个整数拆分为几个数,使拆分乘积最大化,求最大乘积
- 核心推导:数字n拆分后最大乘积可能的来源? 拆2个?3个?最少拆两个吧
- 第一种可能:
i * (n-i)
- 第二种可能:
i * dp[n-i]
- 所以要遍历拆分,取最大
dp[2] = 1 for (let i = 3; i < n + 1; i++) { for (let j = 0; j < i; j++) { dp[i] = Math.max( j * (i - j), j * dp[i - j], dp[i] ) } }
- 第一种可能:
- 不同的二叉搜索树
- 给n个节点,求能组成多少种二叉搜索树
- 初始化:dp[0] = 0; dp[1] = 1
- 核心推导:n个节点组成二叉搜索树和之前的dp[n-1],dp[n-2]..有关系没?
- 有的,遍历n个节点,依次作为根节点
- 那么左子树的种类就是dp[leftNodeNum], 右子树同理
for (let i = 2; i < n + 1; i++) { // 对于i个元素 for (let j = 0; j < i; j++) { // 把第j个元素作为根 const leftNodeNum = j const rightNodeNum = i - j - 1 if (leftNodeNum == 0 || rightNodeNum == 0) { dp[i] += dp[leftNodeNum] dp[i] += dp[rightNodeNum] } else { dp[i] += dp[leftNodeNum] * dp[rightNodeNum] } } }
背包问题
wc上强度了
- 分割等和子集
- 题目理解
- 描述:给一个nums数组,判定该数组是否可以分为两个和相等的子集
- 题意剖析:需要恰好分为两个子集,且和相等,那这个和必然是整个数组之和的一半,那就变成了求子集里,和为sum/2的子集;也就变成了给你一堆物品和一个目标,判断能不能选出若干个物品(每个数只能用一次),使得满足目标,这就变成了01背包问题。至于另一半嘛,如果我能找出一个子集和为总和的一半,那么另一半自动就满足了。
- 最终题目:你有n个物品,这物品i的重量为nums[i],价值为nums[j],你需要判断一个容量为sum/2的背包能装的最大价值是否是sum/2
- DP实现
- dp[i][j]:针对0-i个物品,容量为j的背包能装的最大价值
- 初始化dp[i][0]为0,dp[0][j]遍历判断能不能装第一个物品
- 典型的01背包
for (let i = 1; i < nums.length; i++) { // 遍历物品 let thingValue = nums[i] let thingWeight = nums[i] for (let j = 1; j < partSum + 1; j++) { // 遍历背包容量 if (j < thingWeight) { // 背包容量比物品容量还小,别考虑这个物品了 dp[i][j] = dp[i - 1][j] continue } // 有可能塞进来 const v1 = dp[i - 1][j] // 不塞 const v2 = dp[i - 1][j - thingWeight] + thingValue // 塞 dp[i][j] = Math.max(v1, v2) } }
- 题目理解
- 最后一块石头的重量
- 题目理解
- 描述:给Stones,其中石头两两可以攻击破坏,求最终剩余的石头最小重量
- 题意剖析:两两可破坏,求最终剩最小,那就变成了尽可能分为两组大小相等的石头,这就变成了上一题-求和相等的子集,变成了找重量为sum/2的包的价值
- 最终题目:你有n个物品,每个物品价值为stones[i],重量为stones[i],求重量为sum/2的包的价值和sum/2之间的diff
- 题目理解