算法题目: 509. 斐波那契数
文章中存在些许公式,建议 PC 端打开观感更佳
思路
方阵(行列数相等的矩阵)快速幂
解题过程
- 解析斐波那契数列
-
已知斐波那契数列:
F(n)=⎩ ⎨ ⎧ n,F(n−1)+F(n−2),当 n < 2当 n >= 2
-
我们分析 F(n)=F(n−1)+F(n−2)部分,根据表达式我们可以得到:
F(n)=11F(n−1)F(n−2)
-
通过上述的分析结果,如果想要实现递推,则需要将左右部分演化为相同的规则:
即F(n)F(n−1)与F(n−1)F(n−2)的关系
-
很简单的计算出: F(n−1)=1∗F(n−1)+0∗F(n−2)
即F(n−1)=10F(n−1)F(n−2)
-
所以可以得出:
F(n)F(n−1)=1110F(n−1)F(n−2)
-
可以递推出公式:
F(n)F(n−1)=1110...1110F(1)F(0)
-
相当于需要计算 Mn−1,其中方阵 M为:
M=1110
-
- 快速幂的计算方式
- 假设计算 an,令 m为 n使用二进制表示的最高有效位
- 已知 n>>k为右移操作,且 n & 1=1时表示最低位为 1
- 所以如果 (n>>k) & 1=1则表示第 k+1位为1,否则为 0
- 则:
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 - 继续对计算进行拆分
- 根据上述公式可以得出: an=Sm=i=0∏ma((n>>i) & 1)∗2i
- 当 (n>>i) & 1=0,则 Si=Si−1
- 当 (n>>i) & 1=1,则 Si=Si−1∗a2i
- 其中 a2i为当前项,所以只需要每次迭代完计算下一项即可:
- 初始时第一项为 a20,即 a自身,所以 A0=a
- 计算 a2i+1=a2i∗2=(a2i)2,即 Ai+1=Ai∗Ai
- 定义迭代初始值:以本题为例,则为 2∗2的单位矩阵(对角线全为 1其余全为 0的矩阵),因为单位矩阵乘以相同行列的方阵结果为对应的方阵,即:
S−1=1001 - 以 n>>i作为是否合法的条件进行迭代:
- 计算当前迭代值:
- 当 ((n>>i) & 1=1,则 Si=Si−1∗Ai
- 计算下一项的值: Ai+1=Ai∗Ai
i++,不过可以直接通过n >>= 1修改 n,同时将判断条件也换为 n
- 计算当前迭代值:
复杂度
- 时间复杂度: O(log n)
- 空间复杂度: 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];
};