算法题目: 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)]=[11][F(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)]=[10][F(n−1)F(n−2)]
-
所以可以得出:
[F(n)F(n−1)]=[1110][F(n−1)F(n−2)]
-
可以递推出公式:
[F(n)F(n−1)]=[1110]...[1110][F(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)
class EBS {
result = null;
constructor() {
if (new.target === EBS) {
throw Error("请使用派生类");
}
}
init() {
throw Error("请重写init方法");
}
multiply() {
throw Error("请重写multiply方法");
}
calculate(a, n) {
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));
}
init(max) {
this.#max = max;
const result = this.#initArray();
for (let n = 0; n < max; n++) result[n][n] = 1;
return (this.result = result);
}
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;
}
}
var fib = function (n) {
if (n <= 1) return n;
const ebs = new MatrixEBS();
const M = [
[1, 1],
[1, 0],
];
ebs.init(M.length);
const result = ebs.calculate(M, n - 1);
return result[0][0];
};