算法学习第十二天

329 阅读2分钟

  由于昨晚加班的比较晚,回来的时候已经不够时间学习了,所以昨天停了。今天来一道面试问的比较多的题:leetcode-剑指 Offer 10- II. 青蛙跳台阶问题,这题本身是一道简单题,但其可以引申出很多类似的问题,比如:限定相近两步不能相同的步数?最少跳的步数是多少?不过今天不深究到那地步,恕在下实力不足。今天先讨论原题:

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1
示例 1:
输入:n = 2
输出:2

示例 2:
输入:n = 7
输出:21

示例 3:
输入:n = 0
输出:1

提示: 0 <= n <= 100

  这道题刚入手的时候会有点不知所措,因为找不到规律。其实可以动手简单地计算下,当n=1;n=2;n=3,得到的结果是多少。当n=1时,只能跳一步;当n=2时,可以一次跳两步,也可以跳两次一步;然后当n=3时,可以跳2+1,也可以跳1+1+1,也可以跳1+2,有3种跳法....以此类推,不难发现当前项的步数是前两项的步数之和。
  在数学上,这种叫斐波那契数列,第n项等于第n-1和第n-2项之和。那么转成代码,则是:

var numWays = function(n) {
    if (n <= 1) return 1
    return numWays(n - 1) % 1000000007 + numWays(n - 2) % 1000000007
};

  不过这种解法的时间复杂度是非常高的,为O(2^n),因为中间做了很多重复的运算,比如climbStairs(n - 1) = climbStairs(n - 2) + climbStairs(n - 3)。其实可以使用一个数组把计算过的值保存起来,当后面用到就可以直接使用。

var numWays = function(n) {
    let arr = [0, 1, 2, 3, 5]
    if (arr[n]) {
        return arr[n]
    } else {
        arr[n] = numWays(n - 1) % 1000000007 + numWays(n - 2) % 1000000007
        return arr[n]
    }
};

  优化完毕,这种解法的时间复杂度仅为O(n),但需要理解会递归的思想。还有第三种解法:动态规则,时间复杂度也是为O(n),不过思路和上面那种相反。上面的是从上自下,而这种解法是自下往上。

var numWays = function(n) {
    let l = 0, r = 1
    while (n > 0) {
        let res = l % 1000000007 + r % 1000000007;
        l = r;
        r = res;
        n--;
    }
    return r
};

  反而第三种解法会比较好理解一点,从第0项开进行相加,一直加到第n项。即可得到结果。
  还有第四种解法,利用斐波那契数列的通项公式,可以再研究研究下。其实在这一题中只有第三种解法是不会超时,开始的两种解法都会出现超时。因为n最大可以到100。通用公式解法不会超时,但因为计算出来的数过大会丢失精准度,并且取不了模。今天内容就到这。