题目如下:
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
提示:
1 <= n <= 45
解法思路:
我们用 f(x) 表示爬到第 x 级台阶的方案数,考虑一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子:
f(x)=f(x−1)+f(x−2)
它意味着爬到第 x 级台阶的方案数是爬到第 x−1级台阶的方案数和爬到第 x−2级台阶的方案数的和。很好理解,因为每次只能爬 1 级或 2 级,所以 f(x) 只能从 f(x−1) 和 f(x−2) 转移过来,而这里要统计方案总数,我们就需要对这两项的贡献求和。
以上是动态规划的转移方程,下面我们来讨论边界条件。
我们是从第 0 级开始爬的,所以从第 0 级爬到第 0 级我们可以看作只有一种方案,即 f(0)=1;
从第 0 级到第 1 级也只有一种方案,即爬一级,f(1)=1。
这两个作为边界条件就可以继续向后推导出第 n 级的正确结果。
不妨写几项来验证一下,根据转移方程得到 f(2)=2,f(3)=3,f(4)=5,……,我们把这些情况都枚举出来,发现计算的结果是正确的。
我们不难通过转移方程和边界条件给出一个时间复杂度和空间复杂度都是 O(n) 的实现,但是由于这里的 f(x) 只和 f(x−1) 与 f(x−2) 有关,所以我们可以用「滚动数组思想」把空间复杂度优化成 O(1)。
//动态规划
int climbStairs(int n) {
if (n <= 2) //如果i为0或1或2时,答案分别是0/1/2
return n;
//若n>2
int dp[n+1]; //设置一个n+1的数组,每一个元素代表到达第i个台阶的方法数(0弃置不用)
dp[1] = 1; //到达第一个台阶的方法数
dp[2] = 2; //到达第二个台阶的方法数
//以第一个和第两个台阶的方法数为基础,依次计算后续的台阶的方法数
for (int i = 3; i <= n; i++) //注意判断条件是<=,因为要得到到达第n个台阶的方法数
{
dp[i] = dp[i-1] + dp[i-2];
//由于步长为1或2,因此第i个台阶的方法数必定等于前两个台阶的方法数之和
}
return dp[n];
}
解法分析:
两个写法不同,但思路一致。
首先可以确定,到达第一个台阶和第二个台阶的方法数分别为1和2;又因为步长为1或2;
那么就可以以此判断到达第三个台阶的方法数为第一个台阶的方法数+第二个台阶的方法数,因为第一个台阶跨两步就能到达第三个台阶,第二个台阶跨一步就可以到达第三个台阶,这里隐含的意义是第三个台阶有两个方法:①在第一个台阶上跨两步②在第二个台阶上跨一步,而到达第一个台阶的方法数是1,到达第二个台阶的方法数是2,故到达第三个台阶的方法数=1+2=3。
以此类推,到达第i个台阶的方法有两个:①在第i-2个台阶上跨两步②在第i-1个台阶上跨一步,而在计算第i个台阶时,你肯定已算出到达第i-2个台阶的方法数和到达第i-1个台阶的方法数。因此只需要将这两个方法数相加即可。
感悟:
动态规划问题我认为是将一个总体的问题通过某个规律或者定理将其局部化成一个局部的问题,再对该局部性的问题进行求解,该解法可以通过多次局部化来解决整体的动态规划问题。
比如这道题中,我们可以知道到达第一个台阶和第二个台阶的方法数分别为1和2;
又由题意我们可以得到一个规则:我们跨步的步长只能为1或者2。
那么我们就可以得出如下结论,如果我想到达第i个台阶,在局部上我有两个选择(即两个方法):①在第i-1个台阶上跨一步。②在第i-2个台阶上跨2步。
由此我们可以得到到达第i个台阶的公式way[i]=way[i-2]+way[i-1]。
而要求解到达第i个台阶的方法数,就需要到达第i-1个台阶和第i-2个台阶的方法数。想要知道到达第i-1个台阶需要几个方法数,令i=i-1,可以得到way[i-1]=way[i-3]+way[i-2],第i-2个台阶同理。
我们已知到达第1个和第2个台阶的方法数,就可以求得第3个台阶的方法数,继而求得到达第4个台阶的方法数......以此类推,从而得到到达第i个台阶的方法数,返回该方法数即可求解。