本文已参与「新人创作礼」活动,一起开启掘金创作之路。
Dynamic Programming
动态规划
DP问题
目的:空间换时间,提升效率
方式:找出有规律性的递推公式以及限定条件
动态规划关键词
-
每一步最优
-
将问题划分为重复的子问题【到
n-1步为整体 + 接下来的后一步】- 所以每次问题的形式是一致的,只是具体数据不同
- f_n = f_{n-1} + 第n步
- f_{i,j} ...
-
把问题以归纳方式从最基础范围开始递进
- 因为第n步的结果就是前
n-1步得到的最优结果加上第n步的结果,取最优得到 - 因此避免重复计算,需要记录每步的结果
- 因为第n步的结果就是前
何时使用动态规划
问结果但不是具体内容的,求的是具体一个数,一般用DP比如
-
计数问题
- 多少种路径
- 多少种方法
-
求最大最小值
- 子序列长度(最长、最短)
- 数字和
-
求存在性
- 先手是否取胜
- 能不能选k个数使和最大
问所有种类的结果的内容,一般递归,深度搜索这些。
而,递归做了很多重复计算,因此可以将计算结果保存下来,避免重复计算。
动态问题的一般性处理步骤
一、确定状态
也就是f_n,f_{i,j}之类的
把问题分割为2部分:
-
最后一步
-
子问题
- 把问题的规模缩小,但是问题都是一致的,同样的问题,求得范围更小
二、转移方程
就是递进的规律方程,把子问题和最后一步的联系规律找到了
比如:
f_X = min{f_{X-2}+1, f_{X-5}+1, f_{X-7}+1}
这里表达了f_x(最后一步)和f_{X-}(子问题)的关系
三、初始条件和边界情况
初始条件:
最开始(小范围)的数值,用于初始化,人工设定
(这些初始值用转移方程是算不出来的,然后后面递进计算又要用到的值)
比如:
- 一维情况下,f_0,f_1
- 二维情况下,f_{0,0},f_{0,1},f_{1,0}
这个维数和遍历对应,也和需要的变量对应
边界条件:
对于每次迭代计算,都要在合理的边界下计算
比如不存在的情况下,返回null
四、计算顺序
从前往后、从小往大、从大到小等。。。
顺序原则:看计算要依赖的方向,也就是转移方程中,谁靠谁得到后续的结果
demo1 青蛙跳石头
问题:在学习视频,1:10:52的地方
-
有n块石头分别在x轴的0,1,.... n-1位置·一只青蛙在石头0,想跳到石头n-1
-
如果青蛙在第i块石头上,它最多可以向右跳距离a_i;
-
问青蛙能否跳到石头n-1
-
例子∶
- 输入:a=[2, 3, 1, 1, 4]
- 输出:True
- 输入:a=[3, 2, 1, 0, 4]
- 输出:False
思路(按照方法先自己想的) :
-
一、确定状态,分离子问题和最后一步
蛙能否跳到n-1,能否跳到n-2,...,能否跳到1。
-
二、确定转移方程
f_n = (f_0 + rest_0) || (f_1 + rest_1) || ... || (f_{n-1} + rest_{n-1}) || (f_n + rest_n)
rest_i表示在石头n_i处剩下需要跳的数,跳过了,就true了
有一个true就返回true
-
三、确定边界和初始值
就n块石头
f_0=true
-
四、整体总结
需要一个一维变量
boolean [n] re,记录是否可以跳到对应下标的石头上所以,数组
re应该初始化为false,能跳再改为true -
五、代码(没验证,凭感觉走的)
public boolean jump(int[] stone){
// stone数组就是题目示例的数组a
//定义数组re存结果
n = stone.length;
int[] re = new int[n];
re[0] = true; //初始化
for(int i = 0; i < n; i++){
//得到在每块石头上的结果
for(int j = 0; j < stone[i]; j++){
if(re[j]){//代表可以跳到这块石头上
re[i+j] = true; //把能跳的地方
}
}
}
return re[n-1];
}
\