文章目录
转自大佬博客总结
![在这里插入图片描述](https://img-blog.csdnimg.cn/20201114113354844.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3lsX3B1eXU=,size_16,color_FFFFFF,t_70#pic_center)
0. 前言
养成计算时间复杂度的习惯。
养成做 lc
尽量从算法角度去考虑,而不是暴力角度去考虑问题的习惯。
多算、多算、多算,不是为了做题而做题。
1. 输入数据分析
计算量在 1 0 7 10^7 107 以下最优。到了 1 0 8 10^8 108 算法常数得非常小才可以。
考虑算法常数,例如,二分、排序、哈希的算法常数就很小,循环内部操作的次数比较少。而平衡树的算法常数就很大,因为每次操作会涉及都几个、十几个的指针操作,就很慢…常数就很大,在卡数据、卡常的时候就很无奈。
<=30
的数据,大多都是指数级别的复杂度。dfs
,状压 dp
。
<100
的数据,适合
O
(
n
3
)
O(n^3)
O(n3) 算法,差不多到了
1
e
6
1e6
1e6 计算量,刚好。还有各式各样的 dp
问题。
<1000
的数据,适合
O
(
n
2
)
O(n^2)
O(n2) 或者
O
(
n
2
l
o
g
n
)
O(n^2logn)
O(n2logn) 算法,各式各样 dp
,二分、朴素版 dijkstra
、朴素版 prim
、Bellman-Ford
这几个经典的最短路算法。
<10000
的数据,这几个算法只是听说过…块状链表、分块、莫队要求都蛮高的,很难写。
<1e5
的数据,的数据方案是最常见的,各式各样的
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn) 可供选择。
<1e6
的数据,log 1e6
大约是 20
,即,20*1e6
,为 2000w
的计算量,还是可以过的。但是一定是需要常数比较小的算法。针对 1e6
的算法还是采用
O
(
n
)
O(n)
O(n) 算法居多。
<1e7
的数据,一定是
O
(
n
)
O(n)
O(n)。线性算法,线性筛、kmp
、双指针等。
n<1e9
以上的数据,都是些固定的几个了,大多都是整个题目中的一个小步骤,预处理啥的。参照上图即可。
2. 递归问题及主定理
- 主定理参考百度百科,可以通过主定理求解递归问题的算法时间复杂度
- 一般分析递归问题的话,例如快速排序、归并排序,我们都是将其分层,每层都是 O ( n ) O(n) O(n) 的时间,总共 l o g n logn logn 层。所以总的算法时间就是 O ( n l o g n ) O(nlogn) O(nlogn)。采用这样的方式来估计,能大致估计出一个范围。
3. 双指针
双指针算法:是可以将
O
(
n
2
)
O(n^2)
O(n2) 优化到
O
(
n
)
O(n)
O(n) 的,同理也可将
O
(
n
3
)
O(n^3)
O(n3) 优化掉一个
n
n
n。平时看着是 for (i、j)
里面套 while(i, j)
,但是由于 i、j
都是只加不减的,所以循环体内部总共只会执行 n
次,则双指针算法就是线性的,
O
(
n
)
O(n)
O(n) 的。
4. 单调栈和单调队列
[单调栈+模板] 单调栈模板
[单调队列+模板] 单调队列模板
单调栈和单调队列和双指针算法分析类似,每个元素只会进栈、出一次,所以也是线性的。
5. kmp
kmp
这个时间复杂度分析也是如此,看着是有两层循环,但是当外层 for i,j
,i
每次增加时,匹配时 j
最多只会加 1。从定义出发,j
是严格小于 ne[j]
的,所以失配时 j=ne[j]
的操作总共最多也只会执行 n
次。所以,kmp
也是线性的
O
(
n
+
m
)
O(n+m)
O(n+m)。
6. 并查集
find
看着是 O(n)
,但是有路径压缩存在,就是
l
o
g
n
logn
logn,要是再加上按秩合并,则变为
l
o
g
l
o
g
n
loglogn
loglogn,近似线性的时间复杂度。
7. 堆
取堆顶 O ( 1 ) O(1) O(1),向上调整和向下调整和高度相关,且堆为一颗完全二叉树,则高度为 O ( l o g n ) O(logn) O(logn)。至于删除、添加都是以向上调整和向下调整来完成的,故都是 O ( l o g n ) O(logn) O(logn)。关于建堆这个事情,有神奇的 O ( n ) O(n) O(n) 建堆方法 [堆+模板] 模拟堆模板,在里面也讨论了其时间复杂度证明方法。
8. 哈希表
平均意义下,增删改查都是
O
(
1
)
O(1)
O(1),赌 RP
,不会发生冲突。
9. 搜索dfs与bfs
脑补 dfs
、bfs
搜索树,一般是阶乘或者指数级别的。计算 dfs
的函数执行次数就行了。以全排列为例:
10. 图的dfs、bfs及拓扑排序
[图+拓扑排序+模板] 有向图的拓扑序列(bfs+拓扑排序+模板)
一般有判重的 st
数组存在,所以每个点、每条边只会被遍历一次,所以时间复杂度就是
O
(
n
+
m
)
O(n+m)
O(n+m)。·n
是点,m
是边
同样,拓扑排序基于图的 bfs
,所以也是
O
(
n
+
m
)
O(n+m)
O(n+m)
11. 五大最短路及三大最小生成树
具体分析见:[图+最短路+模板] 五大最短路常用模板
- 朴素版
dijkstra
:两重循环 O ( n 2 ) O(n^2) O(n2) - 堆优化版
dijkstra
:看着是两重while
循环,但是实际内部保证了每个点的所有边只会做一次计算,且每次运算要有一次堆的push
操作,所以是 O ( m l o g m ) O(mlogm) O(mlogm) 时间 Bellman-Ford
算法:两重循环 O ( n 2 ) O(n^2) O(n2)SPFA
算法:最坏会被卡成 O ( n m ) O(nm) O(nm),但是实际运行很快,一般为 O ( m ) O(m) O(m)- 判断负环的话大多都是 O ( n m ) O(nm) O(nm),所以题目数据范围一般不大,2000 * 10000 左右已经是天花板了
Floyd
算法:三重循环, O ( n 3 ) O(n^3) O(n3)
- 朴素
Prim
算法:两重循环 O ( n 2 ) O(n^2) O(n2) - 堆优化
Prim
算法:堆优化将遍历求最值操作的 n n n 优化到 l o g n logn logn,故为 O ( n l o g n ) O(nlogn) O(nlogn) Kruskal
算法:实际算法本身加上并查集后使查找每条边变成 O ( 1 ) O(1) O(1),故内部为 O ( m ) O(m) O(m),关键有个排序,使得总时间变为 O ( m l o g m ) O(mlogm) O(mlogm)
12. 染色法与匈牙利算法、二分图
染色法: 实际上就是图的一个 dfs
或者 bfs
过程,有判重存在,故为
O
(
n
+
m
)
O(n+m)
O(n+m)
匈牙利算法:最坏每次判断需要遍历所有点的所有边,则最坏效率是 O ( n m ) O(nm) O(nm) 的。但是实际运行效率非常快,远远小于 O ( n m ) O(nm) O(nm)
13. 素数筛算法
试除法: O ( n 2 ) O(n^2) O(n2) 或者 O ( n n ) O(n\sqrt n) O(nn)
朴素埃筛:千万别手残写成朴素埃筛… O ( n l o g n ) O(nlogn) O(nlogn)
埃筛: O ( n l o g l o g n ) O(nloglogn) O(nloglogn)
线性筛: O ( n ) O(n) O(n)
14. 最大公约数与快速幂
gcd
:
O
(
l
o
g
n
)
O(logn)
O(logn)
貌似有个库函数 __gcd(a, b)
,lcm
不清楚有没有
快速幂:和 k k k 位数相关,则为 O ( l o g k ) O(logk) O(logk) 的级别
15. 逆元、扩展欧几里得、各式各样组合数
重点在于数学公式的推导,多为
O
(
l
o
g
n
)
O(logn)
O(logn) 级别,依赖于 gcd
。
16. 背包与dp
dp
看循环就行了,计算量=状态数量*状态转移数量
关键是状压 dp
,以 [状压dp] 蒙德里安的梦想(模板题+状压dp) 为例,其三重循环
2
n
2^n
2n、
2
n
2^n
2n、
m
m
m 就是
O
(
m
(
2
n
)
2
)
O(m(2^n)^2)
O(m(2n)2) 的计算量。
关于各类背包问题的时间复杂度分析,参考:[背包] 背包问题算法模板(模板) 即可。
17. 贪心
大多都是排序 O ( n l o g n ) O(nlogn) O(nlogn),要么再嵌套循环啥的,比较好分析,重点是思路。
18. 空间复杂度分析
1 Byte = 8 bit
1 KB = 1024 Byte
1 MB = 1024*1024 Byte
1 GB = 1024*1024*1024 Byte
一般程序题为 64 MB
空间限制,大约开 int
数组的话可以开:2^26 / 4 = 2^24, 1600w 个 int
空间,但是要是真刚好开这么大的话,还是太憨憨了。程序的函数、寄存器啥的也是要空间的。
值得注意的一点是:申请一个小空间和申请一个大空间的花费时间几乎一样的,即准备申请一个 1000 大小的 int
数组,一次申请 1000 个和 每次申请 1 个,进行 1000 次申请,可理解为时间相差 1000 倍。
申请空间但是不用,即便超内存了,也不会报错。但是如果使用的话就会 MLE
。这是操作系统的优化,申请大空间,它不会立即给你,而是在使用中发现没给你的时候会立即分配。
进行空间复杂度分析的时候,除了开的数组什么的固定空间,还要注意栈空间,典型的例子就是快速排序与归并排序的空间复杂度分析对比。