瘦鱼-博客

跳台阶问题分析

问题描述:

         一个台阶,一次可以跳3级或者5级,跳到第n级有多少种跳法。

问题分析:

         刚开始的思路是,每次跳3级或者5级,不一定能跳到第n级,要求n是3的倍数,或者是5的倍数,或者是3i和5j的和(i>=0,j>=0)。所以考虑三种情况:

1、  n是3的倍数;

2、  n是5的倍数;

3、  3i+5j=n(i>=0且j>=0)。其他情况则是不可到达第n级。

情况1和情况2很容易,直接求倍数就可以。情况3考虑的是两层for循环,使用蛮力法找到满足条件的i和j的值。后来发现,情况1和情况2不需要在算法中写具体实现,不满足情况1和情况2,不可到达第n级,则跳法为0。

  另一种是递归。递归要满足两个条件:1、存在相同的情景,可以重复调用;2、存在最终的结束条件。

  最终的结束条件很容易想到,那就是最后只剩下3级台阶或者5级台阶的情况,一次可以跳完,只有一种跳法。相同的情景可以这么考虑,开始跳之前,还剩下n级台阶没有跳过,跳过一次后,比如跳3级或者5级,则剩下n-3或者n-5,再开始第二次跳,发现第二次跳和第一次跳面临的选择是相同的,也是跳3级或者5级,只是要跳的台阶数减少了3级或者5级。那么每次的跳是相同的情景,可以重复调用。如果用函数jumpCount(n)计算跳法的话,n级台阶的跳法即是jumpCount(n),按照递归的思路,如果第一次跳3级的话,则剩下n-3,再跳这n-3级台阶,这n-3级台阶的跳法是jumpCount(n-3)。同理,如果第一次跳5级的话,则剩下n-5级的跳法是jumpCount(n-5);以此递推,每次都是在剩下的台阶数中跳3级或者5级,剩下n-3或者n-5,再重复跳,一直到最后只剩下3级或者5级。那么n级台阶的跳法是jumpCount(n)= jumpCount(n-3)+ jumpCount(n-5)。其实这里发现和第一种思路的第3中情况是类似的。

         这里当时有个思想误区,首先确实是考虑到了将剩下的台阶数作为下次跳跃的总数,每次跳跃进行递归的调用。但是受第一种思路的影响,每次都在考虑n-3i或者n-5j的情况,没有想到递归的核心思想是整体与局部的思想,问题分大的模块来看待,每个小模块再一步一步细分。每个大模块和小模块是相似的关系。每个小模块只需要考虑一步,不需要考虑整体情况,所以不需要考虑i和j了。

问题验证:

蛮力法代码:

 1     /**
 2      * 思路一:非递归,蛮力法找到满足条件的值
 3      * @param n 待跳的台阶数
 4      * @return 跳法
 5      */
 6     public static int jumpCount1(int n) {
 7         int count = 0;//跳法
 8         //i最大不能超过n的约数,同理j
 9         for (int i = 0; i <= n / 3; i++) {
10             for (int j = 0; j <= n / 5; j++) {
11                 if (3 * i + 5 * j == n) {
12                     count++;//满足条件,则找到了一种跳法
13                 }
14             }
15         }
16 
17         return count;
18     }

递归代码:

 1     /**
 2      * 思路二:递归
 3      * @param n 待跳的台阶数。每次跳3级或者5级
 4      * @return 跳法
 5      */
 6     public static int jumpCount(int n) {
 7         if (n < 3) {
 8             return 0;//如果小于3,则跳不了
 9         }
10 
11         if (n == 3 || n == 5) {
12             return 1;//只剩下3级或者5级,则只有一种跳法,跳3级或者5级,一次跳完
13         }
14 
15         return jumpCount(n - 3) + jumpCount(n - 5);//每次跳跃,剩下为n-3或者n-5,作为下次待跳跃的台阶数
16     }

蛮力法时间复杂度为O(n^2),递归为O(n),递归时间复杂度优于蛮力法。递归算法明显优于蛮力法,简介清晰,但是数据量大的情况下运行效率较低,内存开销大。

总结:

1、  蛮力法很容易想到,思想误区是要跳出判断是否能跳到第n级台阶的情况,这种情况跳法就是0,不需要代码中判断。

2、  递归法要牢牢抓住递归的思想,考虑整体就不要管部分,考虑部分就不要管整体。

 

更正

经其他博友提示,蛮力法有问题。

蛮力法只找到了跳完n级台阶,需要跳3级多少次,跳5级多少次,满足n级台阶则算一次。比如说8级台阶,需要跳3级台阶1次,5级台阶1次。但是实际上有两种跳法:1、先跳3级,再跳5级。2、先跳5级,再跳3级。有两种,但是按照蛮力法只有一次,蛮力法应该算组合。

算得跳3级i次,跳5级j次,则总共需要跳i+j次,i+j次中,3级台阶有多少种可能,剩下的就是5级台阶,每种可能就是一种跳法(或者算5级台阶有多少种可能)。其实就是i+j中的i组合。

Ci+ji

 1   public static int jumpCount1(int n) {
 2     int count = 0;// 跳法
 3     // i最大不能超过n的约数,同理j
 4     for (int i = 0; i <= n / 3; i++) {
 5       for (int j = 0; j <= n / 5; j++) {
 6         if (3 * i + 5 * j == n) {
 7           // count++;// 满足条件,则找到了一种跳法
 8           count = count + C(i, i + j);//i+j次跳跃中,跳3级台阶的可能次数,每次算一种跳法(找到了3的位置,则剩下为5的位置。或者找跳5级的位置也可以)
 9         }
10       }
11     }
12 
13     return count;
14   }
15 
16   // 求排列数
17   private static int A(int up, int bellow) {
18     int result = 1;
19     for (int i = up; i > 0; i--) {
20       result *= bellow;
21       bellow--;
22     }
23     return result;
24   }
25 
26   // 求组合数
27   private static int C(int up, int below) {
28     // 分母
29     int denominator = A(up, up);// A(6,6)就是求6*5*4*3*2*1,也就是求6的阶乘
30     // 分子
31     int numerator = A(up, below);// 分子的排列数
32 
33     return numerator / denominator;
34   }

感谢博友的指正!

posted @ 2017-06-13 14:58  瘦鱼  阅读(1239)  评论(2编辑  收藏  举报