矩阵快速幂
快速幂
如果希望求得一个数 \(a\) 的 \(b\) 次幂,一般情况下,暴力的做法就是从 \(1\) 遍历到 \(b\),每次遍历时都将结果乘上 \(a\),得到最终结果。这种做法的时间复杂度为 \(O(n)\),在实际应用中如果对性能要求不是特别高的话,这样也是可行的。一种更为高效的计算方式是根据计算机存储数据的方式,通过巧妙的运算,可以显著地提升计算幂的计算效率。
具体解释如下:
对于任意的指数 \(b\),如果它是一个整数,那么它一定可以获得对应二进制的形式(\(10010\) 格式),而对于 \(a^b\) 计算来讲,那么它可以划分为如下的格式:
举个例子,假设现在要计算 \(5^{10}\),那么可以划分为如下的形式:
而在计算的过程中,可以对高次幂的计算进行转换,转换为由低次幂累乘的结果:\(5^{8}=5^{4}*5^{4}\),\(5^{4}=5^2*5^2\),从而减少重复计算,提升计算效率
具体实现代码如下:
public long pow(int a, int b) {
if (a == 1 || b <= 0) return 1;
long ans = 1L, t = a;
while (b > 0) {
/*
判断当前所在位是否是幂的有效位,如果是有效位,则说明当前的累乘结果
是最终结果的一部分
*/
if ((b & 1) != 0) {
ans = ans * t;
}
/*
累乘操作,分别计算每个位对应的幂
*/
t = t*t;
b >>= 1;
}
return ans;
}
这种做法的时间复杂度为 \(O(log_{2}b)\)
矩阵快速幂
和上文中 “快速幂” 的思想类似,不过这里将计算的底数 \(a\) 换成了矩阵 \(M\),在矩阵的乘法操作中,可以大幅度降低需要的乘法次数
以求斐波那契数列为例,斐波那契数列值得是符合以下递推公式的数列:
一般情况下,当需要求的斐波那契数不是特别大的时候(如 \(n < 20\)),递归可以解决,当规模比较大的时候,使用动态规划的方式,在 \(n < 10^8\) 的情况下,依旧可以取得能够接受的性能。但是当 \(n\) 的数量大于 \(10^9\) 时,在现有的普通计算机的计算能力下,无法取得较好响应时间
一种优化的方式是结合矩阵的相关知识,在 \(n \geq 2\)的前提条件下, 将 \(F(n - 1)、F(n-2)\) 组合成为一个向量:
那么,对于 \(F(n)\) 来将,它对应的向量为:
根据现有的递推关系,对其进行展开:
根据矩阵乘法,可以对上式进行分解:
令
则有
而对于 \(Mat^{n-1}\) 的计算则可以用上面提到的快速幂进行计算
具体的示例代码如下:
public class Solution {
private static final int mod = (int) 1e9 + 7;
private static final int N = 2;
long[][] mul(long[][] a, long[][] b) {
long[][] c = new long[N][N];
for (int i = 0; i < N; ++i) {
for (int j = 0; j < N; ++j) {
c[i][j] = a[i][0]*b[0][j] + a[i][1]*b[1][j];
c[i][j] %= mod;
}
}
return c;
}
public int cal(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
// 单位矩阵,相当于快速幂中的初始值 1
long[][] ans = new long[][]{
{1, 0},
{0, 1}
};
long[][] mat = new long[][]{
{1, 1},
{1, 0}
};
int k = n - 1;
while (k > 0) {
if ((k & 1) != 0) ans = mul(ans, mat);
mat = mul(mat, mat);
k >>= 1;
}
return (int) ans[0][0];
}
}
时间复杂度:\(O(log_{2}n)\) 为快速幂计算的复杂度
空间复杂度:\(O(1)\) 只需要常量的空间
参考:
[2] https://www.desgard.com/algo/docs/part2/ch01/3-matrix-quick-pow/