首先看一下快速幂的思想,假设现在要求 10^75
的结果,我们将指数的二进制求出来,75 的二进制:1001011,也就是 64+8+2+1,所以 10^75
就可以拆分为 10^64+10^8+10^2+10^1
,那么我们可以从 10^1
发出,每次指数都两个增长,然后看最开始的指数和当前指数做 & 运算,如果不为 0,说明该指数可以加到结果集中:
public int fastPower(int p, int n) { | |
//p 为幂,n 为指数 | |
int s = p, idx = 1, res = 1; // 一开始从 p^1 开始 | |
while(idx <= n) { | |
if((idx & n) != 0) { | |
res = res * s; | |
} | |
s *= s; | |
idx = idx << 1; | |
} | |
return res; | |
} |
通过斐波那契数列来实战一下快速幂,斐波那契数列:1,1,2,3,5,8,...
除了前两项以外,f (n) = f (n-1)+f (n-2),通过快速幂算法实现 O (logN) 的时间复杂度。
迭代式是一个二阶递推数列,可以用矩阵乘法来表示:为了方便解释,我假设矩阵
1 1
1 0
为矩阵 A
f(n) = 1*f(n-1) + 1*f(n-2) | |
f(n-1) = 1*f(n-1) + 0*f(n-2) | |
# 联合两个方程可知 | |
(f(n), f(n-1)) = (f(n-1),f(n-2)) * A | |
(f(n-1), f((n-2)) = (f(n-2),f(n-3)) * A | |
... | |
(f(3),f(2)) = (f(2),f(1)) * A | |
#递推可知 | |
(f(n), f(n-1)) = (f(2),f(1)) * (A^(n-2)) |
所以本题就变成了如何快速求 A^(n-2),快速幂的思想就可以用了
private int[][] fastPower(int[][] m, int n) { | |
if(m == null) return null; | |
int row = m.length, col = m[0].length, idx = 1; | |
int[][] res = new int[row][col], tmp = m; | |
// 初始化为单位矩阵 | |
for(int i = 0; i < row; i++) res[i][i] = 1; | |
while(idx <= n) { | |
if((idx & n) != 0) { | |
res = muli(res, tmp); | |
} | |
tmp = muli(tmp,tmp); | |
idx <<= 1; | |
} | |
return res; | |
} | |
// 两个矩阵相乘,这里就不判断这两个矩阵的合法性了 | |
private int[][] muli(int[][] m1, int[][] m2) { | |
if(m1 == null || m2 == null) return null; | |
int row = m1.length, col = m2[0].length; | |
int[][] res = new int[row][col]; | |
for(int i = 0; i < row; i++) { | |
for(int j = 0; j < col; j++) { | |
for(int k = 0; k < col; k++) { | |
res[i][j] += m1[i][k]*m2[k][j]; | |
} | |
} | |
} | |
return res; | |
} |
最后给出解决代码:
public int f(int n) { | |
if(n <= 2) return 1; | |
// (1,1) * A^(n-2) | |
int[][] m = { | |
{1,1}, | |
{1,0} | |
} | |
m = fastPower(m, n-2); | |
// f(n) = 1*m[0][0] + 1*m[1][0], f(n-1) = m[1][0] + m[1][1] | |
return m[0][0] + m[1][0] | |
} |