斐波那契数(快速幂计算的推理过程)

算法题目: 509. 斐波那契数
文章中存在些许公式,建议 PC 端打开观感更佳

思路

方阵(行列数相等的矩阵)快速幂

解题过程

  1. 解析斐波那契数列
    • 已知斐波那契数列:

      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> F ( n ) = { n , 当 n < 2 F ( n − 1 ) + F ( n − 2 ) , 当 n > = 2 F(n)= \begin{cases} \ n, &当\ n\ <\ 2\\[2ex] F(n-1)+F(n-2), &当\ n\ >=\ 2 \end{cases} </math>F(n)=⎩ ⎨ ⎧ n,F(n−1)+F(n−2),当 n < 2当 n >= 2

    • 我们分析 <math xmlns="http://www.w3.org/1998/Math/MathML"> F ( n ) = F ( n − 1 ) + F ( n − 2 ) F(n)=F(n-1)+F(n-2) </math>F(n)=F(n−1)+F(n−2)部分,根据表达式我们可以得到:

      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> [ F ( n ) ] = [ 1 1 ] [ F ( n − 1 ) F ( n − 2 ) ] \begin{bmatrix} F(n) \\ \end{bmatrix} =\begin{bmatrix} 1 & 1 \\ \end{bmatrix} \begin{bmatrix} F(n-1) \\ F(n-2) \\ \end{bmatrix} </math>[F(n)]=[11][F(n−1)F(n−2)]

    • 通过上述的分析结果,如果想要实现递推,则需要将左右部分演化为相同的规则:

      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 即 [ F ( n ) F ( n − 1 ) ] 与 [ F ( n − 1 ) F ( n − 2 ) ] 的关系 即 \begin{bmatrix} F(n) \\ F(n-1) \\ \end{bmatrix} 与 \begin{bmatrix} F(n-1) \\ F(n-2) \\ \end{bmatrix} 的关系 </math>即[F(n)F(n−1)]与[F(n−1)F(n−2)]的关系

    • 很简单的计算出: <math xmlns="http://www.w3.org/1998/Math/MathML"> F ( n − 1 ) = 1 ∗ F ( n − 1 ) + 0 ∗ F ( n − 2 ) F(n-1)=1*F(n-1)+0*F(n-2) </math>F(n−1)=1∗F(n−1)+0∗F(n−2)

      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 即 [ F ( n − 1 ) ] = [ 1 0 ] [ F ( n − 1 ) F ( n − 2 ) ] 即 \begin{bmatrix} F(n-1) \\ \end{bmatrix} =\begin{bmatrix} 1 & 0 \\ \end{bmatrix} \begin{bmatrix} F(n-1) \\ F(n-2) \\ \end{bmatrix} </math>即[F(n−1)]=[10][F(n−1)F(n−2)]

    • 所以可以得出:

      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> [ F ( n ) F ( n − 1 ) ] = [ 1 1 1 0 ] [ F ( n − 1 ) F ( n − 2 ) ] \begin{bmatrix} F(n) \\ F(n-1) \\ \end{bmatrix} =\begin{bmatrix} 1 & 1 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} F(n-1) \\ F(n-2) \\ \end{bmatrix} </math>[F(n)F(n−1)]=[1110][F(n−1)F(n−2)]

    • 可以递推出公式:

      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> [ F ( n ) F ( n − 1 ) ] = [ 1 1 1 0 ] . . . [ 1 1 1 0 ] [ F ( 1 ) F ( 0 ) ] \begin{bmatrix} F(n) \\ F(n-1) \\ \end{bmatrix} =\begin{bmatrix} 1 & 1 \\ 1 & 0 \\ \end{bmatrix}... \begin{bmatrix} 1 & 1 \\ 1 & 0 \\ \end{bmatrix} \begin{bmatrix} F(1) \\ F(0) \\ \end{bmatrix} </math>[F(n)F(n−1)]=[1110]...[1110][F(1)F(0)]

    • 相当于需要计算 <math xmlns="http://www.w3.org/1998/Math/MathML"> M n − 1 M^{n-1} </math>Mn−1,其中方阵 <math xmlns="http://www.w3.org/1998/Math/MathML"> M M </math>M为:

      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> M = [ 1 1 1 0 ] M=\begin{bmatrix} 1 & 1 \\ 1 & 0 \\ \end{bmatrix} </math>M=[1110]

  2. 快速幂的计算方式
    • 假设计算 <math xmlns="http://www.w3.org/1998/Math/MathML"> a n a^n </math>an,令 <math xmlns="http://www.w3.org/1998/Math/MathML"> m m </math>m为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n使用二进制表示的最高有效位
    • 已知 <math xmlns="http://www.w3.org/1998/Math/MathML"> n > > k n>>k </math>n>>k为右移操作,且 <math xmlns="http://www.w3.org/1998/Math/MathML"> n & 1 = 1 n\ \&\ 1=1 </math>n & 1=1时表示最低位为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1
    • 所以如果 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( n > > k ) & 1 = 1 (n>>k)\ \&\ 1=1 </math>(n>>k) & 1=1则表示第 <math xmlns="http://www.w3.org/1998/Math/MathML"> k + 1 k+1 </math>k+1位为1,否则为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0
    • 则:
      <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> n = ( n & 1 ) ∗ 2 0 + ( ( n > > 1 ) & 1 ) ∗ 2 1 + . . . + ( ( n > > m ) & 1 ) ∗ 2 m a n = a ( n & 1 ) ∗ 2 0 + ( ( n > > 1 ) & 1 ) ∗ 2 1 + . . . + ( ( n > > m ) & 1 ) ∗ 2 m a n = a ( n & 1 ) ∗ 2 0 ∗ a ( ( n > > 1 ) & 1 ) ∗ 2 1 ∗ . . . ∗ a ( ( n > > m ) & 1 ) ∗ 2 m \begin{align} n & =(n\ \&\ 1)*2^0+((n>>1)\ \&\ 1)*2^1+...+((n>>m)\ \&\ 1)*2^m\\ a^n & =a^{(n\ \&\ 1)*2^0+((n>>1)\ \&\ 1)*2^1+...+((n>>m)\ \&\ 1)*2^m} \\ a^n & =a^{(n\ \&\ 1)*2^0}*a^{((n>>1)\ \&\ 1)*2^1}*...*a^{((n>>m)\ \&\ 1)*2^m} \\ \end{align} </math>nanan=(n & 1)∗20+((n>>1) & 1)∗21+...+((n>>m) & 1)∗2m=a(n & 1)∗20+((n>>1) & 1)∗21+...+((n>>m) & 1)∗2m=a(n & 1)∗20∗a((n>>1) & 1)∗21∗...∗a((n>>m) & 1)∗2m
    • 继续对计算进行拆分
      • 根据上述公式可以得出: <math xmlns="http://www.w3.org/1998/Math/MathML"> a n = S m = ∏ i = 0 m a ( ( n > > i ) & 1 ) ∗ 2 i a^n=S_m=\mathop{\prod}\limits_{i=0}^m {a^{((n>>i)\ \&\ 1)*2^i}} </math>an=Sm=i=0∏ma((n>>i) & 1)∗2i
      • 当 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( n > > i ) & 1 = 0 (n>>i)\ \&\ 1=0 </math>(n>>i) & 1=0,则 <math xmlns="http://www.w3.org/1998/Math/MathML"> S i = S i − 1 S_i=S_{i-1} </math>Si=Si−1
      • 当 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( n > > i ) & 1 = 1 (n>>i)\ \&\ 1=1 </math>(n>>i) & 1=1,则 <math xmlns="http://www.w3.org/1998/Math/MathML"> S i = S i − 1 ∗ a 2 i S_i=S_{i-1}*a^{2^i} </math>Si=Si−1∗a2i
      • 其中 <math xmlns="http://www.w3.org/1998/Math/MathML"> a 2 i a^{2^i} </math>a2i为当前项,所以只需要每次迭代完计算下一项即可:
        • 初始时第一项为 <math xmlns="http://www.w3.org/1998/Math/MathML"> a 2 0 a^{2^{0}} </math>a20,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> a a </math>a自身,所以 <math xmlns="http://www.w3.org/1998/Math/MathML"> A 0 = a A_0=a </math>A0=a
        • 计算 <math xmlns="http://www.w3.org/1998/Math/MathML"> a 2 i + 1 = a 2 i ∗ 2 = ( a 2 i ) 2 a^{2^{i+1}}=a^{2^{i}*2}=(a^{2^{i}})^2 </math>a2i+1=a2i∗2=(a2i)2,即 <math xmlns="http://www.w3.org/1998/Math/MathML"> A i + 1 = A i ∗ A i A_{i+1}=A_i*A_i </math>Ai+1=Ai∗Ai
      • 定义迭代初始值:以本题为例,则为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 ∗ 2 2*2 </math>2∗2的单位矩阵(对角线全为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 1 </math>1其余全为 <math xmlns="http://www.w3.org/1998/Math/MathML"> 0 0 </math>0的矩阵),因为单位矩阵乘以相同行列的方阵结果为对应的方阵,即:
        <math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> S − 1 = [ 1 0 0 1 ] S_{-1}= \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ \end{bmatrix} </math>S−1=[1001]
      • 以 <math xmlns="http://www.w3.org/1998/Math/MathML"> n > > i n>>i </math>n>>i作为是否合法的条件进行迭代:
        • 计算当前迭代值:
          • 当 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( ( n > > i ) & 1 = 1 ((n>>i)\ \&\ 1=1 </math>((n>>i) & 1=1,则 <math xmlns="http://www.w3.org/1998/Math/MathML"> S i = S i − 1 ∗ A i S_i=S_{i-1}*A_i </math>Si=Si−1∗Ai
        • 计算下一项的值: <math xmlns="http://www.w3.org/1998/Math/MathML"> A i + 1 = A i ∗ A i A_{i+1}=A_i*A_i </math>Ai+1=Ai∗Ai
        • i++,不过可以直接通过n >>= 1修改 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n,同时将判断条件也换为 <math xmlns="http://www.w3.org/1998/Math/MathML"> n n </math>n

复杂度

  • 时间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( l o g n ) O(log\ n) </math>O(log n)
  • 空间复杂度: <math xmlns="http://www.w3.org/1998/Math/MathML"> O ( 1 ) O(1) </math>O(1)
JavaScript 复制代码
// 快速幂 Exponentiation by Squaring
class EBS {
  result = null;
  constructor() {
    if (new.target === EBS) {
      throw Error("请使用派生类");
    }
  }
  init() {
    throw Error("请重写init方法");
  }
  multiply() {
    throw Error("请重写multiply方法");
  }
  calculate(a, n) {
    // 获取单位值:数字为1,矩阵则为单位矩阵
    // 单位矩阵需要手动设置方阵的阶数
    let result = this.result === null ? this.init() : this.result;
    while (n) {
      if (n & 1) {
        result = this.multiply(result, a);
      }
      n >>= 1;
      a = this.multiply(a, a);
    }
    return (this.result = result);
  }
}

// 方阵(行列数相等的矩阵)快速幂
class MatrixEBS extends EBS {
  #max = 0;
  #initArray() {
    return new Array(this.#max).fill(0).map(() => new Array(this.#max).fill(0));
  }
  // 定义单位矩阵:即对角线全为1其余全为0的矩阵
  init(max) {
    this.#max = max;
    const result = this.#initArray();
    for (let n = 0; n < max; n++) result[n][n] = 1;
    return (this.result = result);
  }
  // 计算a*b:方阵a和b为相同的行列数
  multiply(a, b) {
    const c = this.#initArray();
    const max = this.#max;
    for (let i = 0; i < max; i++) {
      for (let j = 0; j < max; j++) {
        let sums = 0;
        for (let k = 0; k < max; k++) {
          sums += a[i][k] * b[k][j];
        }
        c[i][j] = sums;
      }
    }
    return c;
  }
}

/**
 * @param {number} n
 * @return {number}
 */
var fib = function (n) {
  if (n <= 1) return n;
  // 先计算 [[1, 1], [1, 0]] 的 n-1 次幂
  const ebs = new MatrixEBS();
  const M = [
    [1, 1],
    [1, 0],
  ];
  ebs.init(M.length);
  const result = ebs.calculate(M, n - 1);
  // 再乘以[[1], [0]],相当于直接取其第一行第一列的值
  return result[0][0];
};
相关推荐
丶小鱼丶16 分钟前
栈算法之【有效括号】
java·算法
小徐不徐说3 小时前
每日一算:华为-批萨分配问题
数据结构·c++·算法·leetcode·华为·动态规划·后端开发
菜鸟555556 小时前
图论:最小生成树
算法·图论
2401_872945097 小时前
【补题】Codeforces Round 735 (Div. 2) C. Mikasa
算法
叫我:松哥7 小时前
基于网络爬虫的在线医疗咨询数据爬取与医疗服务分析系统,技术采用django+朴素贝叶斯算法+boostrap+echart可视化
人工智能·爬虫·python·算法·django·数据可视化·朴素贝叶斯
Star在努力7 小时前
14-C语言:第14天笔记
c语言·笔记·算法
赴3359 小时前
Numpy 库 矩阵数学运算,点积,文件读取和保存等
人工智能·算法·numpy·random·dot
自由随风飘9 小时前
机器学习-SVM支持向量机
算法·机器学习·支持向量机
屁股割了还要学10 小时前
【C语言进阶】柔性数组
c语言·开发语言·数据结构·c++·学习·算法·柔性数组
草莓熊Lotso10 小时前
【LeetCode刷题指南】--有效的括号
c语言·数据结构·其他·算法·leetcode·刷题