Ref:
- https://oi-wiki.org/math/quick-pow/
- https://zhuanlan.zhihu.com/p/95902286
矩阵快速幂
Ref: https://leetcode-cn.com/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode-solution/
以上的方法适用于 n 比较小的情况,在 n 变大之后,O(n) 的时间复杂度会让这个算法看起来有些捉襟见肘。我们可以用「矩阵快速幂」的方法来优化这个过程。
首先我们可以构建这样一个递推关系:
因此:
令:
因此我们只要能快速计算矩阵 M 的 n 次幂,就可以得到 f(n) 的值。如果直接求取 M^n,时间复杂度是 O(n) 的,我们可以定义矩阵乘法,然后用快速幂算法来加速这里 M^n 的求取。
如何想到使用矩阵快速幂
- 如果一个问题可与转化为求解一个矩阵的 nn 次方的形式,那么可以用快速幂来加速计算
如果一个递归式形如
,即齐次线性递推式,我们就可以把数列的递推关系转化为矩阵的递推关系,即构造出一个矩阵的 n 次方乘以一个列向量得到一个列向量,这个列向量中包含我们要求的 f(n)。一般情况下,形如
可以构造出这样的 m×m 的矩阵:
那么遇到非齐次线性递推我们是不是就束手无策了呢?其实未必。有些时候我们可以把非齐次线性递推转化为其次线性递推,比如这样一个递推:
我们可以做这样的变换:
于是就可以使用矩阵快速幂求解了。并不是所有非齐次线性都可以化成齐次线性,我们还是要具体问题具体分析。
留两个思考题:
你能把 f(x) = 2f(x - 1) + 3f(x - 2) + 4cf(x)=2f(x−1)+3f(x−2)+4c 化成齐次线性递推吗?欢迎大家在评论区留言。
如果一个非齐次线性递推可以转化成齐次线性递推,那么一般方法是什么?这个问题也欢迎大家在评论区总结。
代码:
Java
public class Solution {
public int climbStairs(int n) {
int[][] q = {{1, 1}, {1, 0}};
int[][] res = pow(q, n);
return res[0][0];
}
public int[][] pow(int[][] a, int n) {
int[][] ret = {{1, 0}, {0, 1}};
while (n > 0) {
if ((n & 1) == 1) {
ret = multiply(ret, a);
}
n >>= 1;
a = multiply(a, a);
}
return ret;
}
public int[][] multiply(int[][] a, int[][] b) {
int[][] c = new int[2][2];
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 2; j++) {
c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j];
}
}
return c;
}
}
Golang
type matrix [2][2]int
func mul(a, b matrix) (c matrix) {
for i := 0; i < 2; i++ {
for j := 0; j < 2; j++ {
c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j]
}
}
return c
}
func pow(a matrix, n int) matrix {
res := matrix{{1, 0}, {0, 1}}
for ; n > 0; n >>= 1 {
if n&1 == 1 {
res = mul(res, a)
}
a = mul(a, a)
}
return res
}
func climbStairs(n int) int {
res := pow(matrix{{1, 1}, {1, 0}}, n)
return res[0][0]
}
复杂度分析
时间复杂度:同快速幂,O(logn)。
空间复杂度:O(1)。