2019-03-14 跳台阶

2019-03-14  本文已影响0人  做梦枯岛醒

题目出自《剑指offer》,整理自https://www.nowcoder.com/practice/8c82a5b80378478f9484d87d1c5f12a4

1. 问题

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

2. 思路

首先我选择的是循环,每次有跳1个台阶或2个台阶的可能,然后减去跳过的台阶,但是接下来就没有思路了。
于是又考虑递归,考虑分治法,考虑第一次跳有2种方法,第二次又有两种。考虑到边界条件,递归外边的target = 1,target = 2 这两种情况就分别返回1,2即可

      //特殊情况
        if(target == 1){
            return 1 ;
        }
        
        if(target == 2){
            return 2;
        }
        
        return getNum(target);

重点来看下递归体,重要部分我暂时没放出来,终止条件很好理解,就是只剩1和2的情况,直接返回1和2

  public int getNum(int target){
        if(target == 1){
            return 1;
        }else if(target == 2){
            return 2;
        }else{
            return ?????;
        }
    }

对于 ????? 部分,我一开始写的是。

2 * getNum(xxx);

显然不对,xxx不知道要传什么了,是传target - 1 还是 target - 2?无解,
结合刚才所提到的分治法,我们的目的就是把大问题划分成小问题去求解,
我们第一次跳1个台阶,那么接下来就剩target - 1 的台阶,对于target - 1就是一个子问题,我们逐步缩小规模,直到最后产生最小的子问题,剩1个,或者剩2个。

那这样分析过后,就显得清楚了许多,我写了如下内容

 return getNum(target - 1) + getNum(target - 2);

思考递归,千万不要刨根问底。
思考递归,千万不要刨根问底。
思考递归,千万不要刨根问底。

这是一个忠告,站在宏观的角度上理解递归,懵不懵逼是程序自己的事儿,不是程序猿的事儿,作为一个程序猿,你只需要在大局下知道你这个语句是求了个啥,你就不害怕递归了。

上面的求了个啥?
为啥这样写,还不是因为我们思考上一个代码的时候,传参不会写了?
那么既然-1也不合适,-2也不合适,分成两块好了。

第一块求如果是-1 是啥情况呢?
第二块求如果是-2 是啥情况呢?

相加,Perfect,所有的情况都包含了。

但一旦你试图去理解,-1是怎么运行的,子问题还得-2 ,balabala……
那么恭喜你已经进入了下面这个状态!

如果你说了,你上面讲的宏观 我才做不到呢,你给我讲讲怎么运行的!
好好好,那我就给你画图说一说吧。

image.png

瞧瞧,瞧瞧,这优雅的画技。

首先target = 5进入递归,调用getNum(target - 1) 与 getNum(target - 2)
产生了4和3两个子问题,4和3说,我不知道答案,我去问问我孩子。

然后产生了3,2 和2,1,同样他们3说我也不知道,我得去问我孩子,而其他的都说我知道,分别返回了自己的值。

最后3也找到了它的孩子2,1,分别返回了自己的值。

那么问题来了,值是多少?
刚才说了,2是返回2的,1是返回1的,
你数数树杈子,看看到底是几嘛!

什么还不懂?
斐波那契数列了解一下。

3.完整代码

public static int JumpFloor(int target) {
    
     
      if(target == 1){
          return 1 ;
      }
      
      if(target == 2){
          return 2;
      }
      
      return getNum(target);
  }
  
  
  public static int getNum(int target){
      if(target == 1){
          return 1;
      }else if(target == 2){
          return 2;
      }else{
          return getNum(target - 1) + getNum(target - 2);
      }
  }

4. 运行

先去吃饭,吃完饭更!

仔细看上面的递归树,会发现有很多重复的调用


由此看来性能也不会很好,所以我们就要考虑循环版本了。
https://www.nowcoder.com/profile/214250/codeBookDetail?submissionId=1520111

public class Solution {
    public int JumpFloor(int target) {
        if(target <= 0) return 0;
        if(target == 1) return 1;
        if(target == 2) return 2;
        int one = 1;
        int two = 2;
        int result = 0;
        for(int i = 2; i < target; i++){
            result = one+ two;
            one = two;
            two = result;
        }
        return result;
    }
}

for循环内,是不断更新斐波那契数的过程。

两种算法的算法复杂度分析如下:

递归算法:

二叉树的高度是 n - 1,由我们的基础知识可以知道,一个高度为k的二叉树最多可以由 2^k - 1个叶子节点,也就是递归过程函数调用的次数,所以时间复杂度为 O(2^n),而空间复杂度就是树的高度 S(n)

循环算法:

时间:O(n)
空间:O(1)

5. 结束

结束了,没啥说的,最近复习复试很焦躁,月底就要结束这煎熬了,这一年很充实,希望复试成功吧!

上一篇下一篇

猜你喜欢

热点阅读