这是我参与11月更文挑战的第18天,活动详情查看:2021最后一次更文挑战。
前言
前面我们写了动态规划地去求斐波那契数列,本篇文章会讲解写出时间复杂度为 log(n) 的斐波那契数列解法所需要的知识点。
再试
线性代数
线性代数怎么来的?
这肯定就要说一下线性方程组啦,那什么是线性方程组呢?
{ a1x1 + a2x2 + a3x3 = b1,
a4x4 + a5x5 + a6x6 = b2,
.... }
就是类似这种的方程组,线性方程就是其中的一行。
方程组越写越长,越写越多,于是干脆就把线性方程组简写成了矩阵。
|
a1 a2 a3
a4 a5 a6
|
这里写出来的效果不太好,同学们可以自行百度搜索一下。
矩阵的出现就帮助我们计算更加复杂算法,让一代代大学生都倒在线性代数下。
大学里学的线性代数都差不多还给老师了,这里就不误人子弟了,这里参考的链接放在文章最末尾了,感兴趣的同学可以去看一下,这里我们只需要知道我们即将用到矩阵就行。
本文需要使用到矩阵的乘法运算,运算如下:
|
|a1 , a2|,
|a3 , a4|
| × |
|b1 , b2|,
|b3 , b4|
| = |
|a1 b1 + a2 b3 , a1 b2 + a2 b4|,
|a3 b1 + a4 b3 , a3 b2 + a4 b4||
二阶常系数齐次线性递推数列
斐波那契数列 属于 二阶常系数齐次线性递推数列。
二阶常系数齐次线性递推数列是什么?
二阶常系数齐次线性递推数列就是满足an = c1a(n-1) + c2a(n-2),其中 c1,c2 为常数,这里的()内的数为下标。
好了,如果是二阶常系数齐次线性递推数列,那就会存在一个二阶的矩阵,可以满足这个数列的规律:|F(n) F(n-1)| = |F(n-1) F(n-2)| * A。A 是一个矩阵。
不懂也没关系,我们可以类比成 X 的 N 次方,我们可以通过移动比特位的方式去降低时间复杂度了,而不是一次次地相乘。
时间复杂度降低到log(n)的秘诀
我们来写一个 X 的 N 次方。
先是用普通的 for 循环来写一遍:
private long calculationPower(long base, long power) {
long result = 1;
for (; power != 0; power--) {
result *= base;
}
return result;
}
如果是 2 的 100 次方,那就要运行 100 次,时间复杂度为 n。
当我们优化成通过移动比特位的方式:
private long calculationPower1(long base, long power) {
long result = 1;
long temp = base;
for (; power != 0; power = power >> 1) {
if ((power & 1) != 0) {
result *= temp;
}
temp *= temp;
}
return result;
}
代码都是可以运行的,结果准确无误。
100 的二进制等于 1100100,也就是说,本来要循环 100 次,现在只需要循环 7 次!
效率提升了 14 倍!
我刚学的时候也有一点不懂。
- 为啥 temp 在每次循环中都要自乘?
- 为啥在
(power & 1) != 0时才result *= temp;。
第一点,因为每次 power 右移的时候,其实是在给我们的次方数除于 2,temp 存储的是当前比特位是 X 的 n 次方的结果。最直观的例子就是拿二进制来看一下,6 =110,这三位二进制数分别代表 4 2 1,代入到 2 ^ 6 中,到二进制110中最前面的 1 时,2 =10,result = 4,就是才乘了两次,剩下的这个 1 就代表这 2 ^ 4,而这时的 temp 在上一次循环中做了一次自乘(4 * 4),已经达到了 2 ^ 4,所以 temp 要自乘。
第二点,就跟转换二进制到十进制一样,肯定是当前比特位为 1 才能算是有啊。
(第一点写的我心力憔悴,愣是想了很久才表达出来,个人感觉只要举得例子能看懂就行,其他的可能我个人表达也有问题。)
最后
本篇我们已经明白了怎么把时间复杂度从 O(n) 减少到 O(log(n))的原理,但我们要写出时间复杂度为O(log(n))的解法还需要做一件事情。
未完待续。。。
这里是程序员徐小白,【每日算法】是我新开的一个专栏,在这里主要记录我学习算法的日常,也希望我能够坚持每日学习算法,不知道这样的文章风格您是否喜欢,不要吝啬您免费的赞,您的点赞、收藏以及评论都是我下班后坚持更文的动力。