想了解更多數據結構以及算法題,可以關注微信公衆號“數據結構和算法”,每天一題爲你精彩解答。也可以掃描下面的二維碼關注
問題一:
一隻青蛙一次可以跳上一級臺階,也可以跳上二級臺階,求該青蛙跳上一個n級的臺階總共需要多少種跳法。
我們來分析一下:
當n等於1的時候,只需要跳一次即可,只有一種跳法,記f(1)=1
當n等於2的時候,可以先跳一級再跳一級,或者直接跳二級,共有2種跳法,記f(2)=2
當n等於3的時候,他可以從一級臺階上跳兩步上來,也可以從二級臺階上跳一步上來,所以總共有f(3)=f(2)+f(1);
同理當等於n的時候,總共有f(n)=f(n-1)+f(n-2)(這裏n>2)種跳法。
所以大家一看就知道這就是個斐波那契數列,只不過有一點不同的是斐波那契數列一般是以1,1,2,3,5,8,13……開始的,而我們這是以1,2,3,5,8,13……開始的,少了最前面的一個1。最代碼很簡單
1 public static int f(int n) {
2 if (n < 3)
3 return n;
4 return f(n - 1) + f(n - 2);
5 }
我們以計算f(6)爲例畫個圖看一下計算的過程
我們看到遞歸會重複計算已經計算過的值,效率明顯不是很高,我們還可以把計算過的值儲存起來,防止重複計算,我們來看下代碼
1 private static int f2(int n, HashMap<Integer, Integer> map) {
2 if (n < 3) return n;
3 if (map.containsKey(n))
4 return map.get(n);
5 int first = f2(n - 1, map);
6 int second = f2(n - 2, map);
7 int sum = first + second;
8 map.put(n, sum);
9 return sum;
10 }
我們還可以把遞歸改爲非遞歸的形式,看下代碼
1 private static int f3(int n) {
2 if (n < 3)
3 return n;
4 int first = 1, second = 2, sum = 0;
5 while (n-- > 2) {
6 sum = first + second;
7 first = second;
8 second = sum;
9 }
10 return sum;
11 }
上面3種方式都可以實現青蛙跳臺階問題,那麼哪種效率更高呢,我們來找個比較大的數據測試一下
1 public static void main(String[] args) {
2 int step = 45;
3 long time = System.nanoTime();
4 System.out.println(f(step));
5 System.out.println("代碼優化前時間:" + (System.nanoTime() - time));
6 time = System.nanoTime();
7 System.out.println(f2(step, new HashMap<Integer, Integer>()));
8 System.out.println("代碼優化後時間:" + (System.nanoTime() - time));
9 time = System.nanoTime();
10 System.out.println(f3(step));
11 System.out.println("代碼非遞歸時間:" + (System.nanoTime() - time));
12 }
來看一下運行的時間
1 1836311903
2 代碼優化前時間:2221741900
3 1836311903
4 代碼優化後時間:108000
5 1836311903
6 代碼非遞歸時間:17600
我們看到遞歸優化之前運行時間是非常長的,優化之後時間大幅下降,但對於非遞歸來說又稍遜色了一些。
問題二:
一隻青蛙一次可以跳上一級臺階,也可以跳上二級臺階……,也可以跳n級,求該青蛙跳上一個n級的臺階總共需要多少種跳法。
我們來分析一下
一隻青蛙要想跳到n級臺階,可以從一級,二級……,也就是說可以從任何一級跳到n級,
所以遞推公式我們很容易就能想到
f(n)=f(n-1)+f(n-2)+……+f(2)+f(1)+f(0);最後這個f(0)是可以去掉的,因爲0級就相當於沒跳,所以f(0)=0;
然後我們把f(0)去掉在轉換一下:
f(n-1)=f(n-2)+f(n-3)+……+f(2)+f(1);
所以f(n)=f(n-1)+f(n-1)=2*f(n-1);他是一個等比數列,且f(1)=1;
我們我們可以得出f(n)=2^(n-1);代碼如下
1 private static int f4(int n) {
2 if (n == 1)
3 return 1;
4 return f4(n - 1) * 2;
5 }
或者還可以改爲非遞歸的
1 private static int f5(int n) {
2 if (n == 1)
3 return 1;
4 return 1 << (n - 1);
5 }
問題三:
一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上m級。求該青蛙跳上一個n級的臺階總共有多少種跳法
這道題我們要分開討論:
1,如果n<=m;因爲只能往上跳不能往下跳,所以大於n的都不可以跳,如果跳了就直接超過了,只能跳小於等於n的數字,那麼這個問題就直接退到問題2了。
2,如果n>m;我們要想跳到n級臺階,我們可以從n-1級跳一步上來,或者從n-2級跳兩步上來……,或者從n-m級跳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);(n>m)
所以代碼我們要分爲兩部分,一部分是n>m,另一部分是n<=m,我們來看下代碼
1 public static int f6(int n, int m) {
2 if (n <= 1)
3 return 1;
4 //總檯階大於跳的最高級臺階
5 if (n > m)
6 return 2 * f6(n - 1, m) - f6(n - 1 - m, m);
7 //回退到上面的問題二了
8 return 2 * f6(n - 1, n);
9 }
斐波那契數列又稱黃金分割數列,他有很多的特性,比如兔子的繁殖,他的通項公式如下