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

191 阅读2分钟

算法题目: 509. 斐波那契数

文章中存在些许公式,建议 PC 端打开观感更佳

思路

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

解题过程

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

      F(n)={ n,当 n < 2F(n1)+F(n2),当 n >= 2F(n)= \begin{cases} \ n, &当\ n\ <\ 2\\[2ex] F(n-1)+F(n-2), &当\ n\ >=\ 2 \end{cases}
    • 我们分析F(n)=F(n1)+F(n2)F(n)=F(n-1)+F(n-2)部分,根据表达式我们可以得到:

      [F(n)]=[11][F(n1)F(n2)]\begin{bmatrix} F(n) \\ \end{bmatrix} =\begin{bmatrix} 1 & 1 \\ \end{bmatrix} \begin{bmatrix} F(n-1) \\ F(n-2) \\ \end{bmatrix}
    • 通过上述的分析结果,如果想要实现递推,则需要将左右部分演化为相同的规则:

      [F(n)F(n1)][F(n1)F(n2)]的关系即 \begin{bmatrix} F(n) \\ F(n-1) \\ \end{bmatrix} 与 \begin{bmatrix} F(n-1) \\ F(n-2) \\ \end{bmatrix} 的关系
    • 很简单的计算出:F(n1)=1F(n1)+0F(n2)F(n-1)=1*F(n-1)+0*F(n-2)

      [F(n1)]=[10][F(n1)F(n2)]即 \begin{bmatrix} F(n-1) \\ \end{bmatrix} =\begin{bmatrix} 1 & 0 \\ \end{bmatrix} \begin{bmatrix} F(n-1) \\ F(n-2) \\ \end{bmatrix}
    • 所以可以得出:

      [F(n)F(n1)]=[1110][F(n1)F(n2)]\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}
    • 可以递推出公式:

      [F(n)F(n1)]=[1110]...[1110][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}
    • 相当于需要计算Mn1M^{n-1},其中方阵MM为:

      M=[1110]M=\begin{bmatrix} 1 & 1 \\ 1 & 0 \\ \end{bmatrix}
  2. 快速幂的计算方式
    • 假设计算ana^n,令mmnn使用二进制表示的最高有效位
    • 已知n>>kn>>k为右移操作,且n & 1=1n\ \&\ 1=1时表示最低位为11
    • 所以如果(n>>k) & 1=1(n>>k)\ \&\ 1=1则表示第k+1k+1位为1,否则为00
    • 则:
      n=(n & 1)20+((n>>1) & 1)21+...+((n>>m) & 1)2man=a(n & 1)20+((n>>1) & 1)21+...+((n>>m) & 1)2man=a(n & 1)20a((n>>1) & 1)21...a((n>>m) & 1)2m \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}
    • 继续对计算进行拆分
      • 根据上述公式可以得出:an=Sm=i=0ma((n>>i) & 1)2ia^n=S_m=\mathop{\prod}\limits_{i=0}^m {a^{((n>>i)\ \&\ 1)*2^i}}
      • (n>>i) & 1=0(n>>i)\ \&\ 1=0,则Si=Si1S_i=S_{i-1}
      • (n>>i) & 1=1(n>>i)\ \&\ 1=1,则Si=Si1a2iS_i=S_{i-1}*a^{2^i}
      • 其中a2ia^{2^i}为当前项,所以只需要每次迭代完计算下一项即可:
        • 初始时第一项为 a20a^{2^{0}},即aa自身,所以A0=aA_0=a
        • 计算a2i+1=a2i2=(a2i)2a^{2^{i+1}}=a^{2^{i}*2}=(a^{2^{i}})^2,即Ai+1=AiAiA_{i+1}=A_i*A_i
      • 定义迭代初始值:以本题为例,则为222*2的单位矩阵(对角线全为11其余全为00的矩阵),因为单位矩阵乘以相同行列的方阵结果为对应的方阵,即:
        S1=[1001]S_{-1}= \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ \end{bmatrix}
      • n>>in>>i作为是否合法的条件进行迭代:
        • 计算当前迭代值:
          • ((n>>i) & 1=1((n>>i)\ \&\ 1=1,则Si=Si1AiS_i=S_{i-1}*A_i
        • 计算下一项的值:Ai+1=AiAiA_{i+1}=A_i*A_i
        • i++,不过可以直接通过n >>= 1修改nn,同时将判断条件也换为nn

复杂度

  • 时间复杂度: O(log n)O(log\ n)
  • 空间复杂度: O(1)O(1)
// 快速幂 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];
};