最近在刷一些數據結構的題,發現個很有趣的問題:跳臺階問題。
1. 第一題(引子):輸出菲波那切數列的第N項。
斐波那契數列含義(百度百科):
指的是這樣一個數列:1、1、2、3、5、8、13、21、34、……在數學上,斐波納契數列以如下被以遞歸的方法定義:F(0)=0,F(1)=1, F(n)=F(n-1)+F(n-2)(n>=2,n∈N*)
菲波那切數列是一個很有趣的數列,并且在很多的科研領域都是有應用的(就不介紹有哪些應用了,因為太(wo)高(ye)深(bu)了(hui))。
那用程序怎么實現出來呢?首先根據定義:F(n)=F(n-1)+F(n-2) 作為程序員很容易想到可以使用遞歸來實現。沒錯,下面是遞歸的實現:
public int Fibonacci(int n) {
if(n == 0){
return 0;
}else if( n<=2 ){
return 1;
}
return Fibonacci(n-1)+Fibonacci(n-2);
}
很好理解吧,遞歸的出口就是n=0,n=1,n=2的時候
但是這種做法有什么問題呢?簡單來說這種做法的遞歸的深度是在是太深了。舉個例子:
我們計算n為4的情況:那么我們需要做如下的計算:
Fibonacci(4) = Fibonacci(3) + Fibonacci(2);
= Fibonacci(2) + Fibonacci(1) + Fibonacci(1) + Fibonacci(0);
= Fibonacci(1) + Fibonacci(0) + Fibonacci(1) + Fibonacci(1) + Fibonacci(0);
看看,多做了多少計算。2 計算了 2次,1 計算了5次,0計算了3次。正常來說我們計算4次就可以了吧。這樣相當于多做了4次。
那我們就像能不能用迭代的方式來,觀察下數列,想了下,感覺靠譜:
public static int Fibonacci(int n) {
if(n == 0){
return 0;
}else if( n<=2 ){
return 1;
}
int i1 = 1;
int i2 = 1;
int count = 3;
while (count++<=n) {
int number = i1+i2;
i1 = i2;
i2 = number;
}
return i2;
}
我們知道只需要把上次的計算的值用來作為這次計算了第一項值,來循環最后就求出了我們想要的第n項的值。
看到這你可能想問了:這和跳臺階有什么關系呢?不要著急來看下這個問題:
2. 一只青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
想一想看一看,發現是不是有種似曾相識的感覺,可以把這個問題分解成:
如果兩種跳法,1階或者2階,那么假定第一次跳的是一階,那么剩下的是n-1個臺階,跳法是f(n-1);那么假定第一次跳的是2階,那么剩下的是n-2個臺階,跳法是f(n-2)。那總的問題是不是就變成了F(n)=F(n-1)+F(n-2)。呵呵,這不就是菲波那切數列嗎問題嗎(雖然不是完全一樣,前兩項變成了 1 2了)。代碼如下:
public static int Fibonacci(int n) {
if(n == 0){
return 0;
}else if( n<=2 ){
return 1;
}
int i1 = 1;
int i2 = 1;
//只把這里改成2就可以了,以為這個序列不是 1 1 2 3 5....了,是 1 2 3 5.... 少了個1
int count = 2;
while (count++<=n) {
int number = i1+i2;
i1 = i2;
i2 = number;
}
return i2;
}
看到這里你可能會恍然大悟,大叫原來如此。但你再看下下面這個問題:
3. 一只青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。
如果你足夠聰明,大概已經知道套路了,我們可以看下這個問題怎么分解成一個多項式:
f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n)
分解完了然后怎么辦呢?我們第一個想法可能是用遞歸啊,一個for循環,然后在for循環里遞歸求每個子項,然后等n為0的時候返回1就可以了。
我想說這么做也可以但是你感覺是不是很惡心,這運行起來棧的深度是不是要很深了。
那我們觀察下,看能不能化簡什么的。靈機一動,發下好像可以:
f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
完美,這回在用遞歸次數就明顯下來了。
代碼如下:
```
public int JumpFloorII(int target) {
if (target <= 1) {
return 1;
} else {
return 2 * JumpFloorII(target - 1);
}
}
```
做到這里,咱們是不是自然的想到,我們可以不可以算下:
####4. 一只青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個m級的臺階總共有多少種跳法。
這回我想大家應該都會了這種套路了。我們先不說話,先列多項式:
f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-m)
f(n-1) = f(n-2) + f(n-3) + ... + f(n-m) + f(n-m-1)
化簡得:f(n) = 2f(n-1) - f(n-m-1)
OK,上代碼:
```
public static int JumpFloorIII(int target,int m ) {
//當大于m的時候是上面的公式
if(target > m){
return 2*JumpFloorIII(target-1, m)-JumpFloorII(target-1-m, m);
}
//當小于等于m的時候就是和n級的相同了
if (target <= 1) {
return 1;
} else {
return 2 * JumpFloorIII(target - 1,target);
}
}
```
完美~~~