剑指 Offer 10- I. 斐波那契数列


写一个函数,输入 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。

示例 :

输入:n = 2
输出:1

提示:

  • 0 <= n <= 100

思路:

根据递推关系式F(n) = F(n-1) + F(n-2),很自然会想到递归的方法,其中的终止条件为 n==0n==1的时候。但是在此采用递归的方法,由于内含大量的重复计算,所以会出现超时的现象。

于是改用动态规划的方法,转移方程仍为F(n) = F(n-1) + F(n-2),根据题目的特点,每一次迭代的时候,又两个值可以保存下来,留作下一个迭代使用,避免了大量的重复计算。


方法一:递归(超出时间限制,无法通过)

class Solution {
public:
    int fib(int n) {
        // 以0,1为终止条件
        if(n == 0) return 0;
        else if(n == 1) return 1;
        return (fib(n-1) + fib(n-2)) % 1000000007;
    }
};
  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)

方法二:动态规划

class Solution {
public:
    int fib(int n) {
        const int mod = 1000000007;
		
        // 用 first 和 last 变量分别保存每一次状态转移时的前一个值 与 后一个值
        int first = 0;
        int last = 1;
        int index = 1;
        int sum;
        
        // 特殊情况
        if(n == 0) return first;
        else if(n == 1) return last;
        
        //循环查找
        while(index != n) {
            sum = (first + last) % mod;
            first = last;
            last = sum;
            index++;
        }
        return sum;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

方法三:矩阵快速幂

官方题解

  • 时间复杂度:O(log n)。
  • 空间复杂度:O(1)。


剑指 Offer 10- II. 青蛙跳台阶问题

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

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

示例 2:

输入:n = 7
输出:21

提示:

  • 0 <= n <= 100

思路:
将青蛙跳台阶问题进行抽象化,令跳 n 阶台阶为 f(n) ,那么就有 f(n) = f(n-1) + f(n-2)


方法一:递归(超出时间限制)

class Solution {
public:
    int numWays(int n) {
        // 终止条件
        if(n == 0 || n == 1) return 1;
        if(n == 2) return 2;
        // 返回
        else return ( numWays(n-1) + numWays(n-2) ) % 1000000007;   
    }
};

方法二:动态规划

class Solution {
public:
    int numWays(int n) {
        const int mod = 1000000007;
		
        // 存储变量
        int first = 1;	// 当 n==0 的时候,f(n)==1
        int last = 1;	// 当 n==1 的时候,f(n)==1
        int index = 1;	// index 指向 n=1 (第一次转移方程中的last)
		
        // 特殊情况
        if(n == 0) return first;
        else if(n == 1) return last;
        
        // 相当于先计算 f(2)
        int sum = (first + last) % mod;
        while(index != n) {
            sum = (first + last) % mod;
            first = last;
            last = sum;
            index++;	// 这行代码放在 while 中首行或尾行都可,不会影响结果
        }
        return sum;
    }
};
//附:如果想让 index 指向 n=2,则需要将 while 中的条件更改为 index!=n+1 才可以
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)


剑指 Offer 63. 股票的最大利润

假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
     注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

限制:

0 <= 数组长度 <= 10^5

方法:动态规划

在遍历数组的同时,每次都记录下当前最小的价格 minPrice,并计算在该 minPrice 的基础下能获得的利润 profit ,利用得到的利润更新当前的最大利润 maxProfit

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        
        int minPrice = INT_MAX;
        int profit = 0;
        int maxProfit = 0;

        for(int i = 0; i < prices.size(); i++) {
            if(prices[i] < minPrice) minPrice = prices[i];
            profit = prices[i] - minPrice;
            if(profit > maxProfit) maxProfit = profit;
        }
        return maxProfit;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)


Krahets关于斐波那契的解法总结

公式:f(n+1) = f(n) + f(n-1)

总结:

  1. 递归法:
    • 原理:将 f(n+1) 的问题拆分成 f(n) 和 f(n-1) 这两个子问题,进行递归计算,以 f(0) 和 f(1) 作为终止条件
    • 缺点:大量的重复计算
  2. 记忆递归
    • 原理:新建一个长度为n的数组,用于存储数字值,避免了重复计算
    • 缺点:需要额外的空间
  3. 动态规划:
    • 原理:转移方程f(n+1) = f(n) + f(n-1)