持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第1天,点击查看活动详情
从递归开始
动态规划代码很简单如下
public int climbStairs(int n) {
int pps = 0, ps = 0, s=1;
for(int i = 1; i <= n; i++){
pps = ps;
ps = s;
s = ps + pps;
}
return s;
}
代码很简短,而且最终结果也是正确的,但是有一点点难懂,那这个代码是如何演变成这样子的呢?本博客将把这题梳理一下。 首先假设我们已经爬完了整个楼梯方法为。 现在我们来假设最后一步爬了几个楼梯,根据题目意思有两种可能
- 上了一个楼梯:
- 上了两个楼梯:
只有这种可能,就将这两种可能相加,可以得到递推公式 这样我们就可以使用递归来实现,这也是这个动态规划的第一个雏形。 在写递归代码的时候我们把边界条件算一下
- 只有一阶台阶的时候有一种方法
- 只有两阶台阶的时候有两种方法
下面就是递归的算法
public int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
return climbStairs(n - 1) + climbStairs(n - 2);
}
仔细观察这个递归,我们可以发现最大的嵌套次数是每次都,直到n = 2,那最大嵌套次数就是n - 2次,现在能不能考虑用循环来代替嵌套呢?
现在假设使用一个数组dp[]来记录上到一个台阶有多少种方法,那
就变成了
同时
接下来就可以使用数组加循环来完成这段代码了
public int climbStairs(int n) {
//这个if是防止数组越位的
if(n == 1) return 1;
int[] dp = new int[n + 1];
dp[1] = 1;
dp[2] = 2;
//循环次数是n-3+1(+1 是加上3这个状态)
for(int i = 3; i <= n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
再仔细观察这段for循环,和递归很像,但是有一些区别
- 递归:是从最后一个楼梯开始,往回找,我们并不知道前面的状态,所以只能一个一个去找
- 循环:我们是从第一个楼梯开始,往上爬,前面的状态,我们都知道了,只要把前面的状态拿来用 而在循环中只有三个状态:当前状态 = 前一个状态 + 前前一个状态,拿我们只要把这两个状态保留就可以。
- ppStatus前前状态
- pStatus前状态
- status此状态
如果是三阶楼梯的话,那ppStatus = 1, pStatus = 2,这也是这两个状态的初始值 那就得到以下代码
public int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
//前前状态
int ppStatus = 1;
//前状态
int pStatus = 2;
int status = 0;
for(int i = 3; i <= n; i++){
//当前状态
status = ppStatus + pStatus;
//状态移动,
ppStatus = pStatus;
pStatus = status;
}
return status;
}
这个状态基本上再做开始初始化三个状态分别是-1 0 1 那就得到了我们最开始的代码
有很多表述不到位的地方还见谅
最终代码
class Solution {
public int climbStairs(int n) {
if(n == 1) return 1;
if(n == 2) return 2;
//前前状态
int ppStatus = 1;
//前状态
int pStatus = 2;
int status = 0;
for(int i = 3; i <= n; i++){
//当前状态
status = ppStatus + pStatus;
//状态移动,
ppStatus = pStatus;
pStatus = status;
}
return status;
}
}