1.算法的复杂度
算法的好坏主要从时间复杂度和空间复杂度两个维度衡量:前者衡量运行快慢,后者衡量额外内存占用。早期计算机存储有限,空间复杂度备受关注;如今存储容量大幅提升,我们更优先关注时间复杂度。
2.时间复杂度
2.1 概念
时间复杂度是定量描述算法运行时间的函数 ,以算法中基本操作的执行次数(与问题规模N相关)来衡量运行效率。
2.2 常见时间复杂度计算举例
2.2.1 Fun1:

Func1 执行的基本操作次数 :

**·**N = 10 F(N) = 130
**·**N = 100 F(N) = 10210
**·**N = 1000 F(N) = 1002010
实际中我们计算时间复杂度时,我们其实并不一定要计算精确的执行次数,而只需要大概执行次数,那么这里我们使用大O的渐进表示法。
大O符号(Big O notation):是用于描述函数渐进行为的数学符号。
推导大O阶方法:
1、用常数1取代运行时间中的所有加法常数。
2、在修改后的运行次数函数中,只保留最高阶项。
3、如果最高阶项存在且不是1,则去除与这个项目相乘的常数。得到的结果就是大O阶。
本质:计算算法时间复杂度属于哪个量级
使用大O的渐进表示法以后,Func1的时间复杂度为:
**·**N = 10 F(N) = 100
**·**N = 100 F(N) = 10000
**·**N = 1000 F(N) = 1000000
通过上面我们会发现大O的渐进表示法去掉了那些对结果影响不大的项,简洁明了的表示出了执行次数。
时间复杂度的三种情况:
1.最好:算法执行的最小次数
2.平均:算法执行的期望次数
3.最坏:算法执行的最大次数
实际分析中我们以最坏情况为准。
例如:在长度为N的数组中线性查找元素,最坏情况需要执行N次比较,时间复杂度为O(N)。
2.2.2 Func2:

2.2.3 Func3:

2.2.4 Func4:

2.2.5

2.2.6

2.2.7

由于递归实现的斐波那契数列时间复杂度高达O(2^N),效率极低,因此这个算法意义不大。
我们可以通过迭代方式进行优化,将时间复杂度从O(2^N)降到O(N):

3.空间复杂度
3.1概念:
空间复杂度是衡量算法运行时临时占用存储空间大小的量度,它只计算变量的个数,同样使用大O渐进表示法。
注意:由于函数栈空间是在编译期确定,它主要由运行时显式申请的额外空间决定。
3.2 常见空间复杂度计算举例
3.2.1

使用了常数个额外空间,所以空间复杂度为 O(1)
3.2.2

malloc动态开辟了N+1个数组空间,其他变量都是固定数量,空间复杂度为O(N)
3.3.3

递归调用了N次,开辟了N个栈帧,每个栈帧使用了常数个空间。空间复杂度为O(N)
4.复杂度的oj练习
4.1 消失的数字
https://leetcode.cn/problems/missing-number-lcci/

思路一:

由于思路一依赖排序,时间复杂度为O(NlogN),不能满足要求
思路二:

利用等差数列求和公式Sn=n (a1+an)/2计算0到N的值。

这个思路可行,但是当N太大的时候还是存在溢出风险。
思路三:

这个思路需要我们清楚异或的几个性质:
**1.**相同的值异或后为0
**2.**0异或任何数都是该数本身
3. 异或满足交换律与结合律

4.2 轮转数组
https://leetcode.cn/problems/rotate-array/

数组旋转k次,结果等价于旋转k%N次(N为数组长度)。
基于此,我们可以分析出两种典型情况:

画图能让代码思路更清晰,然后我们根据旋转一次的过程图写代码:
思路一:

但是结果如下:

原因很简单,当数组长度N很大,且k接近N时,这个算法的时间复杂度是O(k*N)≈O(N²),嵌套循环处理大数据会直接超时。
思路二:

依旧通过画图来梳理代码思路:

代码如下:
