剑指 Offer 10. 斐波那契数列和青蛙跳台次数问题

0. 题目来源

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof

1. 题目描述

写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下:

F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。

  • 注意:答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

2. 题目分析与求解

斐波那契数列是非常常见的题目,常常用于学习递归,所以这个题目绝对可以用递归做,另外本题还可以用动态规划的思想做。

2.1 递归的方法

其实看到题目后,我啪的一下就敲出了如下代码,很快的啊,然后提交显示超时,很尴尬。

public class Solution {
    public int fib(int n) {
        if (n == 0 )
            return 0;
        if (n == 1)
            return 1;
        return  (fib(n-1)+ fib(n-2))%1000000007;
    }
}

究其原因,如下图所示,求F(6)时,F(2)求解了5次,F(3)求解了3次,F(4)求解了2次,重复率非常高,内存消耗量特别大。当求解的F(n)中的n越大,重复量越多。
在这里插入图片描述
故做出如下优化:

import java.util.HashMap;
import java.util.Map;

/**
 * @author: sz
 * @date: Created in 2021/1/31
 * @description: 斐波那契数列,用map优化后不超时
 * @version: 1.0
 */

public class Solution4 {
    public int fib(int n) {
        return fib(n,new HashMap<>());
    }

    private int fib(int n , Map<Integer,Integer> map){
        if (n < 2)
            return n;
        if (map.containsKey(n))
            return map.get(n);

        int res = (fib(n-1,map)+fib(n-2,map))%1000000007;
        map.put(n,res);
        return map.get(n);
    }
}

提交结果:
在这里插入图片描述
在leetcode题解中看到一位答主的代码如下所示:

int constant = 1000000007;

public int fib(int n) {
    return fib(n, new HashMap());
}

public int fib(int n, Map<Integer, Integer> map) {
    if (n < 2)
        return n;
    if (map.containsKey(n))
        return map.get(n);
    int first = fib(n - 1, map) % constant;
    map.put(n - 1, first);
    int second = fib(n - 2, map) % constant;
    map.put(n - 2, second);
    int res = (first + second) % constant;
    map.put(n, res);
    return res;
}
作者:sdwwld
链接:https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/solution/di-gui-he-fei-di-gui-liang-chong-fang-shi-du-ji-ba/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

他将结果拆成了first和last两部分,分别放入map中,其实不用这么拆,因为在执行int res = (fib(n-1,map)+fib(n-2,map))%1000000007;map.put(n,res);的时候,已经递归地将除了0和1外的中间重复的数放入map中了。

2.2 动态规划法

根据昨天博客中的思想,我写出如下代码,分析过程见注释:

/**
 * @author: szz
 * @date: Created in 2021/1/31
 * @description: 动态规划法求解斐波那契数列
 * @version: 1.0
 */

public class Solution2 {
    /**
     * 分析一波:
     *  1. 确定状态:
     *      1.1 最后一步  f[n] = f[n-1]+f[n-2]
     *      1.2 化为子问题  f[n-1] = f[n-2]+f[n-3]
     *  2. 转移方程:
     *      f[n] = f[n-1]+f[n-2]
     *  3. 初始条件:f[0] = 0 ,f[1] = 1
     *
     *  4.计算顺序,从头开始
      *
     *
     */
    public int fib(int n) {
        if (n == 0)
            return 0;
        if (n==1)
            return 1;

        int[] f = new int[n+1];
        // 初始条件与边界条件
        f[0] = 0;
        f[1] = 1;

        for (int i = 2; i < n+1; i++) {
            f[i] = f[i-1] + f[i-2];  //转移方程
            f[i] = f[i]%1000000007;
        }
        return f[n];
    }
}

提交结果:
在这里插入图片描述
可以看出内存消耗太高了,因为为了求取最后一次的数,我单独创立了一个数组,存储斐波那契数列,这非常耗内存,所以对代码做出如下优化:

class Solution {
   public int fib(int n) {
       int a = 0, b = 1, sum;
        for(int i = 0; i < n; i++){
            sum = (a + b) % 1000000007;
            a = b;
            b = sum;
        }
        return a;
    }
}

提交结果:
在这里插入图片描述

3. 青蛙跳台阶问题

3.1 问题描述

一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

  • 注:答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。

示例 1:

输入:n = 2
输出:2

示例 2:

输入:n = 7
输出:21

示例 3:

输入:n = 0
输出:1

3.2 问题分析

需要用动态规划做

  1. 确定状态
    设跳上 n级台阶有 f(n)种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。
  • 当为 1 级台阶: 剩 n-1 个台阶,此情况共有 f(n-1) 种跳法;
  • 当为 2 级台阶: 剩 n-2 个台阶,此情况共有 f(n-2) 种跳法。
  1. 转移方程
    由1中分析,可得f(n) 为两种情况之和,即 f(n)=f(n-1)+f(n-2) ,该递推性质为斐波那契数列性质,所以本题可转化为求斐波那契数列第 n 项的值,与上一个问题一样,不同点在于起始点不同。
  2. 边界条件
  • f ( 0 ) = 1 , f ( 1 ) = 1 f(0)=1,f(1)=1 f(0)=1,f(1)=1
  1. 确定顺序
    肯定是从低到高跳,依次确定跳到第0,1,2,3…阶台阶的方法。

3.3 代码实现

package question10_2;

/**
 * @author: szz
 * @date: Created in 2021/2/1
 * @description: 青蛙跳台阶问题
 * @version: 1.0
 */
public class Solution {
    public int numWays(int n) {
        int a = 1, b =1,sum;
        for (int i = 0; i < n; i++) {
            sum = (a+b)%1000000007;
            a = b;
            b= sum;
        }
        return a;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值