左神一周刷爆力扣视频P2
评估算法的核心指标:
- 时间复杂度(流程决定)
- 额外空间复杂度(流程决定)
- 常数时间项(实现细节决定)
何为常数时间的操作?
- 如果一个操作的执行时间不以具体样本量为转移,每次执行时间都是固定时间。称这样的操作为常数时间的操作。
常见的常数时间的操作:
- 常见的算术运算(+,-,*,/,%)
- 常见的位运算(>>(带符号右移), >>>(不带符号右移), <<, |, &, ^等)
- 赋值,比较,自增,自减操作等
- 数组寻址操作
选择排序
- 一个有N个数的数组,先在0—N-1位置上找到最小值在哪,然后把这个最小值和0位置的数交换,全局最小值就来到了0位置,也就是排完序后最小值在最左边。
- 在1—N-1位置上找这个范围内的最小值,找到之后把这个数和1位置上的数交换,则也就是找到了全局第二小的数。
- 在2—N-1的范围上找最小值,找到之后把这个数和2位置上的数交换。
- 以此类推。。。
这个流程发生了多少次常数操作:要分析一下究竟干了什么,
- 首先0—N-1位置上每个数都要看一下(数组寻址),看到时候要和之前找到的最小值作比较,每一个位置就是一次 “看+比”,执行完“看+比”之后,单独做一次交换操作。
- 1—N-1上同样是这样的事,执行“看+比”,然后只执行一次交换。
- 以此类推。。。
一共发生了多少次常数级别的操作:
第一次:N* (看+比)+1(就是N次 “看+比”,和一次的交换)
第二次:N-1*(看+比)+1
第三次:N-2*(看+比)+1
。。。。
其中,“看”这个操作,就是每个数只发生一次,“比”也是每个数只发生一次,
也就是
第一次:N* (2)+1
第二次:N-1*(2)+1
第三次:N-2*(2)+1
。。。
将上面都加起来就是
2*(N+N-1+N-2+…1)+N
前面是2乘以一个等差数列(有等差数列,时间复杂度就是O(N^2))
python
arr = [5, 4, 6, 3, 2, 1]
for i in range(len(arr)):
minindex = i
for j in range(i+1, len(arr)): # i~N-1上找最小值下标
if arr[minindex]>arr[j]:
minindex = j
arr[minindex],arr[i] = arr[i],arr[minindex] # 让最左边的数和最小值交换
for i in arr:
print(i)
c++
// 0~N-1
// 1~N-1
//...
for(int i = 0; i<arr.length;i++)
{
int minindex = i;
for(int j = i+1; j<arr.length; j++) // i ~ N-1 上找最小值下标
{
minindex = arr[j] < arr[minindex] ? j : minindex;
}
swap(arr,i,minindex);
}
如何确定算法流程的总操作数与样本数量之间的表达式关系?
- 想象该算法流程所处理的数据状况,按最差情况来
- 把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作
- 如果数据量为N,看看基本动作的数量和N是什么关系
如何确定算法流程的时间复杂度?
- 当完成了表达式的建立,只要把最高阶项留下即可,低阶项都去掉,高阶项的系数也去掉。记为O(忽略系数的高阶项)
冒泡排序
例如对 5 4 6 3 2 1排序,
从下标0~5开始
- 0~1下标的数,谁大谁在后面,5比4大,5在后面,交换,此时是4 5 6 3 2 1
- 1~2下标数,谁大谁在后面,6大,不用交换,4 5 6 3 2 1
- 2~3下标数,谁大谁在后面,6和3交换,此时是4 5 3 6 2 1
- 3~4下标数谁大谁在后面,6和2交换,4 5 3 2 6 1
- 4~5下标数谁大谁在后面,6和1交换,4 5 3 2 1 6
到此,最大值已找到,接下来在0~4范围内重复这件事
。。。
第二大值找到,在0~3范围内重复这件事
以此类推。。。
常数级别的操作是:
0~5下标数操作时,N-1对儿比较之后决定交换还是不交换
0~4下标数操作时,N-2对儿比较之后决定交换还是不交换
0~3下标数操作时,N-3对儿比较之后决定交换还是不交换
。。。
N-1 + N-2 + N-3 + …等差数列,时间复杂度O(N^2)
arr = [5, 4, 6, 3, 2, 1]
# 0~N-1下标上的数比较
# 0~N-2
# 0~N-3
# ...
for i in range(1,len(arr)): # 共N-1 次
# 0 和 1 之间,谁大谁换
# 1 和 2 之间,谁大谁换
# 。。。以此类推
for j in range(0,len(arr)-i): # 0~N-1之间,谁大谁换,,0~N-2之间,谁大谁换,,,,
if arr[j] > arr[j+1]:
arr[j],arr[j+1] = arr[j+1],arr[j]
for i in arr:
print(i)
# 0~N-1下标上的数比较
# 0~N-2
# 0~N-3
# ...
for(int e = arr.length-1; e>0; e--) // 0~e
{
for(int i = 0; i<e; i++)
{
if(arr[i] > arr[i+1])
{
swap(arr,i,i+1);
}
}
}
插入排序
例如:0~5下标对应数组:
5 4 3 4 3 2
- 0~0范围有序,自然有序,
- 0~1范围有序,1上的数往前看,比前面的小的话就与前者交换,4和5交换,4 5 3 4 3 2
- 0~2范围有序,2上的数往前看,比前面的小的话就与前者交换,直到不再比左边的数小,或者左边没数了,停止,3 4 5 4 3 2
- 0~3范围有序,3上的数往前看,3 4 4 5 3 2
- 0~4:3 3 4 4 5 2
- 0~5:2 3 3 4 4 5
按最差情况计算时间复杂度:例如:6 5 4 3 2 1,O(N^2)
c++
for(int i = 0; i < arr,length; i++) // 0~i做到有序
{
for(int j = i-1; j>=0 && arr[j] > arr[j+1]; j--)
{
swap(arr,j, j+1);
}
}
常见的时间复杂度:
排名从好到差
O(1)
O(logN)
O(N)
O(N*logN)
O(N^2), O(N^3)… O(N^K)
O(2^N), O(3^N),… O(k^N)
O(N!)
二分法
时间复杂度O(logN)
int L = 0;
int R = arr.lenght-1;
int mid = 0;
while(L<R)
{
mid = L + ((R - L) >> 1); // (R+L)/2
if(arr[mid] == num)
{
return true;
}
else if(arr[mid]>num)
{
R = mid -1;
}
else
{
L = mid + 1;
}
}
return arr[L] == num;
认识异或运算
异或运算:相同为0,不同为1
异或运算就记成无进位相加
性质:
a^a == 0
0^a == a
异或运算满足交换律和结合律
不需要创建新变量的数据交换:
a = a^b
b = a^b
a = a^b
三行代码交换a和b,但不建议这么用
用:
tmp = a;
a = b;
b = tmp;
异或练习