经典递归问题:青蛙跳台阶,跳1个,2个或者3个
这个问题经常在各类面试中看到。一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
我们知道如果要写递归代码,那一定要先找到递归公式,这个找法,一个是画执行时的栈结构图来看,一个是从小到大来分析。我们看一下从小到大的过程:

从这里我们可以归纳出,递推公式是f(n)=f(n-1)+f(n-2),然后就可以根据三步走策略来写代码了:
1、确定递归函数功能
假设 f(n) 的功能是求青蛙跳上一个n级的台阶总共有多少种跳法,代码如下:
int f(int n){
}
2、找出递归结束的条件
求递归结束的条件,直接把 n 压缩到很小很小就行了,因为 n 越小,我们就越容易直观着算出 f(n) 的多少,所以当 n = 1时,你知道 f(1) 为多少吧?够直观吧?即 f(1) = 1。代码如下:
int f(int n){
if(n == 1){
return 1;
}
}
3.找出函数的等价关系式
每次跳的时候,青蛙可以跳一个台阶,也可以跳两个台阶,也就是每次跳的时候,小青蛙有两种跳法。
第一种跳法:第一次我跳了一个台阶,那么还剩下n-1个台阶还没跳,剩下的n-1个台阶的跳法有f(n-1)种。
第二种跳法:第一次跳了两个台阶,那么还剩下n-2个台阶还没,剩下的n-2个台阶的跳法有f(n-2)种。
所以,小青蛙的全部跳法就是这两种跳法之和了,即 f(n) = f(n-1) + f(n-2)。至此,等价关系式就求出来了。于是写出代码:
int f(int n){
if(n <=2 ){
return n;
}
return f(n-1) + f(n-2);
}
代码看上去没问题对不对?其实是有和斐波那契数列一样的问题的,当 n = 2 时,显然会有 f(2) = f(1) + f(0)。我们知道,f(0) = 0,按道理是递归结束,不用继续往下调用的,但我们上面的代码逻辑中,会继续调用 f(0) = f(-1) + f(-2)。这会导致无限调用,进入死循环。
关于递归结束条件是否够严谨问题是递归算法的重要一环,有很多人在使用递归的时候,由于结束条件不够严谨,导致出现死循环。我们这里再补充n=2的情况就行了,代码如下:
int f(int n){
//f(0) = 0,f(1) = 1,等价于 n<=2时,f(n) = n。
if(n <= 2){
return n;
}
return f(n-1) + f(n-2);
}
那结束条件该怎么确定呢?其实最简单的方式就是写几个试一试,n足够小的时候也不过为 0 1 2 3 这几个种情况,或者是执行结束或者开始的时候几个元素,带进去试一试看看对不对就行了。
如果一次还能跳3个呢?
拓展如果这里说青蛙每次可以跳1个,2个,或者3个,那该怎么做呢?
一共有n个台阶,那青蛙第一次可以有几种跳法呢?很显然第一次可以选择跳1个,2个,3个,那剩余的台阶数是多少呢?是n-1,n-2,n-3个。而剩余台阶不管是多少个,都可以按照f(n)一样的逻辑来处理,也就是f(n-1)+f(n-2)+f(n-3)。所以我们就得到了递推公式f(n)=f(n-1)+f(n-2)+......+f(n-3)。这明显也是一个递归过程。
我们前面说了,必须处理结束时的场景,所以f(0),f(1),f(2)都要单独标记一下,如果根据上面的说明,我们直接这些这么写其实是有问题的:
int jump(int n){
if(n<=2)return n;
return f(n-1)+f(n-2)+f(n-3);
}
问题在哪里呢?就是n=3的时候其实是有四种的:① 1 1 1 ② 1 2 ③ 2 1 ④ 3。但是这里从3开始就少了一种,为此有两种处理方法,一个是n=0的时候直接返回1,但是0的时候为什么是1呢?没想明白。
还有一种是将条件这么写:
if(n<=3){return 1<<(n-1)}而且如果是m的话,可以直接改成if(n<=m){return 1<<(n-1)},测试了一下这么做确实有效,但是背后的道理是什么呢?也没想明白。