动态规划
一、动态规划
1. 使用题目类型
1.计数
- 有多少种方式走到右下角
- 有多少种方法选出k个数使得和是Sum
2.求最大最小值
- 从左上角走到右下角路径的最大数字和
- 最长上升子序列长度
3.求存在性
- 取石子游戏,先手是否必赢
- 能不能选出k个数使得和是Sum
2. 解题常规套路(以leetcode322最大最小值型为例)
1.确定状态:当我们在利用动态规划解题时,需要创建一个数组,状态就是数组中的每个元素F[i]或者F[i] [j]所代表的含义
- 研究最优策略的最后一步
- 化为子问题:与原问题相比只是规模变小了的问题
- 状态,就是函数f(x) = 原问题与子问题重复部分 + x
- 在动态规划中,通常用数组下标代表变量,下标指向的内容代表状态,即数组元素f[x] = 原问题与子问题重复部分 + x
2.转移方程:阐述了不同状态之间的转换关系
- 可以根据子问题的定义直接得到
- 对于leetcode322而言,其实就是f[x] = min{f[x-2]+1,f[x-5]+1,f[x-7]+1}
3.确定初始条件和边界情况
- 初始条件:一般是指F[0],F[0] [0] 是多少
- 边界条件:一般是指索引是负数怎么办,不能拼出这个值怎么办,跳到矩阵外面怎么办
4.计算顺序
- 一般情况下,一维数组我们都是按照下标从小到大的顺序来计算状态,二维数组都是按照行由上到下,每行从左到右的顺序来计算状态
- 由于计算后面的状态会用到前面的状态,所以这样计算会减少计算量,简化计算
3. 动态规划与贪心算法的异同
3.1 相同
- 两者都要求有最优子结构性质,最优子结构性质即指问题的最优解包含其子问题的最优解。
- 贪心算法是动态规划的一个特例,动态规划是贪心算法的泛化。
3.2 不同
- 贪心自顶向下求解,动态规划自底向上求解。① 贪心:如果把所有的子问题看成一棵树的话,贪心自顶向下,每次向下遍历最优子树即可(通常这个“最优”都是基于当前情况下显而易见的“最优”),这样的话,就不需要知道一个节点的所有子树情况,于是构不成一棵完整的树。② 动态规划:如果把所有的子问题看成一棵树的话,动态规划自底向上,从叶子向根;构造子问题的解,对每一个子树的根,求出下面每一个叶子的值,推得根的值,最后得到一棵完整的树,根的值即为最优值。
- 贪心最优解一定包含上一步的最优解,动态规划最优解不一定包含上一步的最优解。① 贪心:每一步的最优解一定包含上一步的最优解,上一步之前的最优解不作保留。② 动态规划:全局最优解不一定包含上一步的最优解,因此需要记录上一步的所有解。
- 贪心不能保证全局最优,动态规划(本质是穷举法)能保证全局最优。
- 贪心复杂度较低,动态规划复杂度较高。
二、刷题经历
2022.05.15
- leetcode42 接雨水
- 解题方法:动态规划。
- 基本思路:本题的基本思路就是通过
height
每个元素左右两边的最大值计算出height
数组中每个元素位置的存水量,做和即可。 - 具体步骤:① 首先,定义数组
leftLength
,leftLength[i]
代表height[i]
及其左边元素的最大值,则不难看出leftLength[i] = max(leftLength[i - 1], height[i])
。③ 其次,定义数组rightLength
,rightLength[i]
代表height[i]
及其右边元素的最大值,则不难看出,且rightLength[i] = max(rightLength[i + 1], rightLength[i])
。③最后,定义一个capcity
数组,用于存储height
数组每个位置的存水量,则capcaity[i] = min(height[i]左边的最大值, height[i]右边的最大值)- height[i]
。④ 遍历做和即可得到最终结果。
2022.05.24
- leetcode62不同路径
- 解题方法:动态规划
- 基本思路:到达每个位置的路径个数都可以通过到达该位置的上一个位置的路径数与到达该位置的左一个位置的路径数作和求出。
- 具体步骤:① 首先定义一个dp二维数组,dp[i][j]表示有多少种方式可以到达第[i][j]个位置。② 初始化第一行和第一列的元素为1。③ 遍历整个二维数组dp,计算dp数组每个位置的元素值,dp[i][j]=dp[i-1][j-1]。③ 当二维数组dp遍历完成以后,就可以得出最后的结果,返回dp[m-1][n-1]即可。