这个题目虽然只是一个入门题,但是废话也会多一些,记得刚入门动态规划题目的时候,是真的每个字都会读,但就是看不懂…
题目
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数。
示例:
输入: 2 输出: 2
解释: 有两种方法可以爬到楼顶。
- 1 阶 + 1 阶
- 2 阶
示例:
输入: 3 输出: 3
解释: 有三种方法可以爬到楼顶。
- 1 阶 + 1 阶 + 1 阶
- 1 阶 + 2 阶
- 2 阶 + 1 阶
思路
动态规划的思路是将大问题分成很多个小问题解答,也可以理解成高中数学题的递推找规律。
比如这道题目,可以用这种思路解答。
爬到第1阶楼梯有1种方法:1。
爬到第2阶楼梯有2种方法:1+1,2。
爬到第3阶楼梯有3种方法:1+1+1,1+2,2+1。
爬到第4阶楼梯有5种方法:1+1+1+1,1+2+1,2+1+1,1+1+2,2+2
依此类推…
爬到第n阶楼梯有?种方法?这里想不数学题,当k=1时,y=?,k=2时,y=?,当k=n时,y=?。动态规划的题目一定会有一个递推公式的,而且n+1对应的值是由前面求的y值推出来的,将各种可能情况对应的y值求出来就是答案了。
其实最难的地方就是怎么找到递推的公式。我常用的方法有3种,可以作为参考。
- 树状图,求第n个步骤具体的值。
例如这个题目,求第n阶楼梯,画到第n+1层就可以了,因为最长路径就是n个1。如上图,第n阶的方法就是结点值为n的个数。这个根据具体题目画图就可以。 - 表格,根据表格推算dp的值,并验证对推公式是否正确。
用上面的方法如果找不到规律就把值填到表格里面更直观的找。其实最后遍历的也是这个表格。在这里提一下,dp的值是和前面的dp值相关的,后面会接触到一种备忘录的技巧,就是存储要用到部分值。举个例子,这里推递推公式,从n=3开始,满足:dp(n) = dp(n-1)+dp(n-2)
,如果使用备忘录技巧是不需要用到一个数组的,直接定义两个变量存dp(n-1)和dp(n-2)的值,然后遍历的时候同步更新这两个值就可以了。这样在dp数组很大的时候,可以节省存储空间。
- 纯理解。
这道题就可以理解成,到第n阶楼梯只能够从第n-1阶楼梯走1和从第n-2阶楼梯走2得到,所以到达第n阶楼梯的方法应该是第n-1阶楼梯的方法数+第n-2阶楼梯的方法数。关于递推公式的话,动态规划的基本题型都刷一下,后面基本都可以通过画画图加理解能够得到。
关于动态规划,做题得到一个整体的思路,但是仅供参考,因为我刚开始学习算法,学习方法稍微笨拙一些,大佬们可以无视。
- 定义dp数组,自己要清除x和y分别代表什么意思,这道题为例,x就是楼梯阶数,y就是到达第x阶楼梯的方法数。
- 特殊值,就像数学题一样,会有一些不满足规律的值要单拎出来。
- 递推公式,这里遍历求dp的值要注意的问题有要判断什么条件下满足递归公式,然后从哪里开始循环。例如这道题,递推公式是
dp[i] = dp[i-1]+dp[i-2]
,因为数组从0开始,所以至少要从i=2开始遍历,但这道题是从3开始遍历的,是因为还要考虑到是从什么时候开始才满足递推公式。这个具体情况需要具体分析。
代码
class Solution {
//传入楼梯数量
public int climbStairs(int n) {
//1. 定义dp数组
//dp[i]:台阶为 i 时有 dp[i] 种方法
int dp[] = new int[n+1];
//2. 特殊值
//因为从i=3开始递归,所以前面的都是特殊值
//特殊值需要返回或初始化根据题目判断
if(n <= 2) return n;
dp[0]=0;
dp[1]=1;
dp[2]=2;
//递推公式,从i=3开始满足
for(int i=3;i<dp.length;i++){
//到n级有两种方式:从n-1级跳或者从n-2级跳
//所以跳到n级的方法 = 跳到n-1级的方法+跳到n-2级的方法
dp[i] = dp[i-1]+dp[i-2];
}
//上面的数组会将dp数组填写完整,返回dp[n]为最后结果
return dp[n];
}
}