一、快速幂
实现 pow(x, n) ,即计算
x
的整数n
次幂函数(即,xn
)。
对于上面这道题,我们最容易想到的解法就是通过循环或递归进行累乘求解
public double MyPow(double x, int n)
{
long N = n;
if (N == 0)
return 1;
double res = x;
long num = N > 0 ? N : -N;
for (long i = 1; i < num; i++)
{
res *= x;
}
if (N < 0)
res = 1 / res;
return res;
}
然而,这种解法在面对n比较大的情况时,效率会变得十分低下。仔细想想上面的循环过程就会发现,里面进行了很多次无意义的计算。以 a 10 a^{10} a10举例,通过循环一个一个往上乘,需要进行9次运算。而如果我们先计算出 a 2 a^2 a2(1次),然后计算 a 2 ∗ a 2 ∗ a 2 ∗ a 2 ∗ a 2 a^2*a^2*a^2*a^2*a^2 a2∗a2∗a2∗a2∗a2(4次),总计算量就能减少到5次。如果先计算出 a 4 a^4 a4(2次),再计算 a 2 ∗ a 4 ∗ a 4 a^2*a^4*a^4 a2∗a4∗a4(2次),总计算量就能减少到4次。
也就是说,将 a n a^n an拆解成 a x ∗ a y ∗ a z . . . a^x*a^y*a^z... ax∗ay∗az...的形式就可以降低总体运算的次数。那么如何拆解n才最高效呢?根据前面的思路,显然是每次平方所需的运算次数最少。仍然是 a 10 a^{10} a10的例子,我们从 a 1 a^1 a1开始,每次取平方值,可以得到 [ a 1 , a 2 , a 4 , a 8 ] [a^1,a^2,a^4,a^8] [a1,a2,a4,a8]。想要组成 a 10 a^{10} a10,只需要让 a 8 ∗ a 2 a^8*a^2 a8∗a2即可。所需的计算次数也是4次。看到这里有没有觉得熟悉?我们实际上就是在把n拆成几个 2 ? 2^? 2?相加,也就是类似于n的二进制转换成十进制的过程。
下面仍然以
a
10
a^{10}
a10为例,通过图展示一下计算过程:
我们从10的最低位开始遍历,遇到的第一位是0,对应的是
a
1
a^1
a1,但我们并不需要它计算结果,所以跳过。
接下来遇到的第二位是1,对应
a
2
a^2
a2,这是计算结果需要用到的,所以把它乘到res上。继续移动指针。
遇到的第三位是0,对应
a
4
a^4
a4,用不到,跳过。
遇到的第四位是1,对应
a
8
a^8
a8,需要用到,把它乘到res中。
res便是最终结果。将上面的思路转换成代码(未考虑特殊情况):
private double Pow(double a, int n)
{
double res = 1;
while (n !=0)
{
// 当前位为1,计入结果
if ((n & 1) == 1)
res = res * a;
// a自乘
a *= a;
// n右移
n >>= 1;
}
return res;
}
二、矩阵快速幂
矩阵快速幂的一个非常典型的应用就是斐波那契数列(原题地址)。先来看斐波那契数列的定义:
F
(
0
)
=
0
,
F
(
1
)
=
1
F(0) = 0, F(1) = 1
F(0)=0,F(1)=1
F
(
N
)
=
F
(
N
−
1
)
+
F
(
N
−
2
)
,
其中
N
>
1.
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
F(N)=F(N−1)+F(N−2),其中N>1.
我们先定义一个矩阵
[
F
(
N
)
F
(
N
−
1
)
]
\begin{bmatrix} F(N) & F(N-1) \end{bmatrix}
[F(N)F(N−1)]
现在假设有一个变换矩阵
M
M
M,与上述矩阵存在如下关系
[ F ( N ) F ( N − 1 ) ] × M = [ F ( N + 1 ) F ( N ) ] \begin{bmatrix} F(N) & F(N-1) \end{bmatrix}×M= \begin{bmatrix} F(N+1) & F(N) \end{bmatrix} [F(N)F(N−1)]×M=[F(N+1)F(N)]
很显然,
M
M
M是一个
2
×
2
2×2
2×2的矩阵,我们假设
M
=
[
a
b
c
d
]
M= \begin{bmatrix} a & b \\ c & d \end{bmatrix}
M=[acbd]
带入上面的公式可得
[
F
(
N
)
F
(
N
−
1
)
]
×
[
a
b
c
d
]
=
[
a
F
(
N
)
+
c
F
(
N
−
1
)
b
F
(
N
)
+
d
F
(
N
−
1
)
]
=
[
F
(
N
+
1
)
F
(
N
)
]
\begin{bmatrix} F(N) & F(N-1) \end{bmatrix} × \begin{bmatrix} a & b \\ c & d \end{bmatrix}= \begin{bmatrix} aF(N) + cF(N-1) & bF(N) + dF(N-1) \end{bmatrix}= \begin{bmatrix} F(N+1) & F(N) \end{bmatrix}
[F(N)F(N−1)]×[acbd]=[aF(N)+cF(N−1)bF(N)+dF(N−1)]=[F(N+1)F(N)]
也就是
a
F
(
N
)
+
c
F
(
N
−
1
)
=
F
(
N
+
1
)
aF(N) + cF(N-1) = F(N+1)
aF(N)+cF(N−1)=F(N+1)
b
F
(
N
)
+
d
F
(
N
−
1
)
=
F
(
N
)
bF(N) + dF(N-1) = F(N)
bF(N)+dF(N−1)=F(N)
很容易得出
M
=
[
1
1
1
0
]
M = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}
M=[1110]
即
[
F
(
N
)
F
(
N
−
1
)
]
×
[
1
1
1
0
]
=
[
F
(
N
+
1
)
F
(
N
)
]
\begin{bmatrix} F(N) & F(N-1) \end{bmatrix} × \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}= \begin{bmatrix} F(N+1) & F(N) \end{bmatrix}
[F(N)F(N−1)]×[1110]=[F(N+1)F(N)]
根据数学归纳法可得
[
F
(
N
+
1
)
F
(
N
)
]
=
[
F
(
1
)
F
(
0
)
]
×
[
1
1
1
0
]
n
\begin{bmatrix} F(N+1) & F(N) \end{bmatrix}= \begin{bmatrix} F(1) & F(0) \end{bmatrix} × \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} ^n
[F(N+1)F(N)]=[F(1)F(0)]×[1110]n
我们只需要定义出矩阵乘法,配合前面的快速幂,即可实现在
O
(
l
o
g
N
)
O(logN)
O(logN)时间复杂度内解出斐波那契数列。
// 自定义的矩阵类
public class MyMatrix
{
private int[,] _matrix;
public int RowNum { get; }
public int ColNum { get; }
public int this[int row, int col]
{
get => _matrix[row, col];
set => _matrix[row, col] = value;
}
public MyMatrix(int rowNum,int colNum)
{
_matrix = new int[rowNum, colNum];
RowNum = rowNum;
ColNum = colNum;
}
// 矩阵乘法
public MyMatrix Multiply(MyMatrix b)
{
if (ColNum != b.RowNum)
throw new Exception("矩阵1的列数与矩阵2的行数需相同");
MyMatrix newMatrix = new MyMatrix(RowNum, b.ColNum);
for (int i = 0; i < RowNum; i++)
{
for (int j = 0; j < ColNum; j++)
{
for (int k = 0; k < b.ColNum; k++)
{
// 根据题目要求,结果需要对1000000007取余
newMatrix[i, k] = (int)(((long)this[i, j] * b[j, k] + newMatrix[i, k])%1000000007);
}
}
}
return newMatrix;
}
}
public class Solution {
// 程序入口
public int Fib(int n)
{
if (n == 0)
return 0;
if (n == 1)
return 1;
// 定义变换矩阵
MyMatrix a = new MyMatrix(2, 2)
{
[0, 0] = 1,
[0, 1] = 1,
[1, 0] = 1,
[1, 1] = 0
};
MyMatrix res = Pow(a, n);
// 因为[f(1) f(0)] = [1,0],无需计算直接得出f(n)
return res[0, 1];
}
private MyMatrix Pow(MyMatrix a, int n)
{
// 单位矩阵,相当于1
MyMatrix res = new MyMatrix(2, 2)
{
[0, 0] = 1,
[0, 1] = 0,
[1, 0] = 0,
[1, 1] = 1
};
while (n != 0)
{
if ((n & 1) == 1)
res = res.Multiply(a);
a = a.Multiply(a);
n >>= 1;
}
return res;
}
}