从一道经典的动态规划题目 leetcode.70 爬楼梯说起,这道题目典型的动态规划解决,使用滚动数组,能做到时间复杂度O(N) 空间复杂度O(1) 程度上解决问题,这也是我的第一想法。
而官方题解给出的几种基于线性代数的解决方案,能将时间复杂度降低到O(logN),而且所涉及到的知识也是自己所学习过的,但却丝毫没有想到,所以这里稍作复习总结。
矩阵快速幂
矩阵快速幂用到的就是在现有通项的基础上,构建线性关系,把一维的数据变成多维,从而形成若干相同矩阵乘初始项,而这里问题就变成了,如何求某个矩阵的若干次幂?
矩阵快速幂
矩阵快速幂的核心算法还是快速幂,只不过对于普通的快速幂算法,矩阵快速幂计算对象以及乘法运算对应的规则有所不同:
快速幂
求幂运算,一般的,比如 求
2
7
2^{7}
27,我们会计算
2
∗
2
∗
2
∗
2...
2*2*2*2...
2∗2∗2∗2...,这种计算方法是O(N)的,但利用快速幂我们可以将复杂度降低到 O(logN)
基本思想史,数字可以表示为2进制,比如 9(10)=1001(2),那么自然的,我就可以利用二进制参与幂的运算,即
2
9
=
2
1001
=
2
2
3
∗
1
+
2
2
∗
0
+
2
1
∗
0
+
2
0
∗
1
2^9=2^{1001}=2^{2^3*1+2^2*0+2^1*0+2^0*1}
29=21001=223∗1+22∗0+21∗0+20∗1,而进行这样的二进制转换后,则只用进行较少的运算。
代码如下:
int pow_mod(int a,int b)
{
int ans=1;
while(b)
{
if(n&1) ans*=a; //&是与运算符,任何数&1都只会保留二进制最后一位
a*=a
b=b>>1;
}
return ans;
}
矩阵乘法
一般的,我们用二阶数组来表示一个矩阵,而对于普通矩阵,我们有C=A·B
C
i
j
=
∑
k
=
1
n
A
i
k
B
k
j
C_{ij}=\sum_{k=1}^nA_{ik}B_{kj}
Cij=∑k=1nAikBkj
所以矩阵乘法的代码如下:
Matrix operator*(const Matrix &B) const {//Matrix是二阶数组结构体
Matrix ans;
for(int i = 0; i < M; i++){
for(int j = 0; j < M; j++){
for(int k = 0; k < M; k++){
ans.a[i][j] += a[i][k] * B.a[k][j];
}
}
}
return ans;
}
矩阵快速幂
Matrix pow_mod(Matrix A,int n)
{
Matrix B;
for(int i=0;i<2;i++)//初始化单位矩阵
for(int j=0;j<2;j++)//为什么单位矩阵?E*A=A,等效于数字 1*a=a
B.a[i][j]=i==j?1:0;
while(n)//快速幂
{
if(n&1) B=mul(B,A);
A=mul(A,A);
n=n>>1;
}
return B;
}
特别的,对于此道题目,我们最终需要得到的是
class Solution {
public:
int climbStairs(int n) {
vector<vector<long long>> q = {{1, 1}, {1, 0}};
vector<vector<long long>> res = matrixPow(q, n);
return res[0][0];//是因为初始是[1,1]
}
private:
vector<vector<long long>> matrixPow(vector<vector<long long>> &a, int n) {
vector<vector<long long>> ret = {{1, 0}, {0, 1}}; // 初始状态
while (n > 0) {
if ((n & 1) == 1) // 按位取值
ret = matrixMultiply(ret, a);
n >>= 1;
a = matrixMultiply(a, a); // 计算下一位的矩阵值,这里决定数据类型必须用longlong,否则越界风险
}
return ret;
}
vector<vector<long long>> matrixMultiply(vector<vector<long long>> &a, vector<vector<long long>> &b) {
int i = 0, j = 0;
vector<vector<long long>> c = {{0, 0}, {0, 0}};
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;
}
};
//resources: leetcode评论区dl :ryl
通项公式
实际上是求矩阵的幂的一种其它想法,矩阵快速幂更多的是基于计算机计算特性,而通项公式则是基于矩阵分解来求矩阵的幂。
一般的,对于“好的”矩阵A,有
A
=
S
−
1
Δ
S
A=S^{-1}\Delta S
A=S−1ΔS分解,而利用这种分解去求幂将简单得多,比如这道题目中的矩阵
[
1
1
1
0
]
\begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}
[1110],得到特征方程:
λ
(
λ
−
1
)
=
1
λ
2
−
λ
−
1
=
0
Δ
=
1
+
4
=
5
\lambda(\lambda-1)=1 \quad \lambda^2-\lambda-1=0 \quad \Delta=1+4=5
λ(λ−1)=1λ2−λ−1=0Δ=1+4=5
可以借出,矩阵的特征值为
5
+
1
2
1
−
5
2
\frac{\sqrt5+1}{2} \quad \frac{1-\sqrt5}{2}
25+121−5
即矩阵
Δ
=
[
5
+
1
2
0
0
1
−
5
2
]
\Delta=\begin{bmatrix} {\frac{\sqrt5+1}{2}} & 0 \\ 0 & \frac{1-\sqrt5}{2}\end{bmatrix}
Δ=[25+10021−5]
然后求出特征向量带入,或者直接解未知数都可以解决此类问题。