马士兵-左神算法-哔哩哔哩地址
1.复杂度、二分、对数器、异或运算
1.评估算法的优劣的核心指标
1.时间复杂度(流程决定)
分析过程,得出表达式,忽略掉系数、常数项、低阶项,只留下最高阶,最高阶系统去掉
例如:表达式-> ax^3+bx+c 时间复杂度就是:O(x^3)
为什么:当样本量达到一定的次数的时候,就会发现系数、低阶项、常数项都不重要了。
100万*N^2+200万*N +600万,当N->∞, 系数和常数算个毛线啊
2.空间复杂度(流程决定)
3.常数项时间(流程决定)
定义:如果一个操作的执行时间不以样本量转移,每次都是固定时间,就叫做常数操作。
常见的固定时间操作:
常见的算术预算(加减乘除)
常见的位运算(>>,>>> ,<<,|,&,^)
赋值,比较,自增,自减操作
数组寻址操作
>> 带符号右移 右移的时候,左边部位用符号位补位,如果正数就用0补位,负数1补位
>>> 不带符号右移 都用0补位
时间复杂度的意义:
抹掉了好多东西,只剩下了一个最高阶项啊…那这个东西有什么意义呢?
当我们要处理的样本量很大很大时,我们会发现低阶项是什么不是最重要的;
每一项的系数是什么,不是最重要的。真正重要的就是最高阶项是什么。
这就是时间复杂度的意义,它是衡量算法流程的复杂程度的一种指标,
该指标只与数据量有关,与过程之外的优化无关。
如何确定算法流程的总操作数量和样本数量之间的表达式关系?
1.想象该算法流程所处理的数据状况,要按照最差情况来
2.把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作
3.把整个流程彻底拆分为一个个基本动作,保证每个动作都是常数时间的操作
选择排序流程:
arr[0~n-1] 范围上找到最小值,交换到0位置
arr[1~n-1] 范围上找到最小值,交换到1位置
arr[2~n-1] 范围上找到最小值,交换到2位置
arr[n-2~n-1] 范围上找到最小值,交换到n-2位置
选择排序图解
选择排序时间复杂度:
等差数列 1+2+3...+n-1,n*(n-1)/2 O(n^2)
选择排序代码
/**
* 选择排序
* @param arr
*/
public static void SelectSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
for (int i = 0; i < arr.length - 1; i++) {
int minindex = i;
for (int j = i + 1; j < arr.length; j++) {
minindex=arr[j]<arr[minindex]?j:minindex;
}
swap(arr, i, minindex);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
冒泡排序流程:
1.在arr[0~n-1]:arr[0]和arr[1]谁大来到1位置,arr[1]和arr[2]谁大谁来到2位置...
arr[n-2]和arr[n-1],谁大来到n-1位置
2.在arr[0~n-2]:arr[0]和arr[1]谁大来到1位置,arr[1]和arr[2]谁大谁来到2位置...
arr[n-3]和arr[n-2],谁大来到n-2位置
3.在arr[0~n-3]:arr[0]和arr[1]谁大来到1位置,arr[1]和arr[2]谁大谁来到2位置...
arr[n-4]和arr[n-3],谁大来到n-3位置
....
最后在arr[0~1]范围上,重复上面操作,arr[0]和arr[1],谁大来到1位置
时间复杂度: 等差数列 O(N^2)
冒泡排序代码
/**
* 冒泡排序
*
* @param arr
*/
public static void BubbleSort(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
for (int i = arr.length-1; i >=0; i--) {
for (int j = 0; j < i; j++) {
if (arr[j] > arr[j + 1]) {
swap(arr, j, j + 1);
}
}
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
插入排序流程
1.想让arr[0~0]上有序,只有一个数,当然有序
2.想让arr[0~1]上有血,所有从arr[1]开始往前看,如果arr[1]<arr[0],
就交换,否则什么也不做。
3.想让arr[0~i]上有序,所以从arr[i]往前看,arr[i]这个数不停向左移动,
一直移动到左边的数字不在比自己大,停止移动。
.....
n.最后一步,想让arr[1~n-1]上有序,arr[n-1]不同向左移动,一直移动到
左边的数字不在比自己大,停止移动
估算插入排序时间复杂度,会因为数据状况不同而不同!
如果从左到右,如果是已经升序的,每个数往前看,只看1次,这样是最好的情况O(N)
如果从左到右,如果是已经降序的,每个数往前看,要看i次,这样是最坏的情况O(N^2)
额外空间复杂度
你要实现一个算法流程,在实现算法流程的过程中,你需要开辟一些空间来支持你的算法流程。
作为输入参数的空间,也不算额外空间。
作为输出结果的空间,也不算额外空间。
因为这些都是必要的和实现目标有关的,所有都不算。
但除此之外,你的流程如果还需要开辟空间才能让你的流程继续下去。这部分空间就是额外空间。
如果你的流程只需要开辟有限几个变量,额外空间复杂度就是O(1).
算法流程的常数项
时间复杂度,是忽略低阶项和所有常数系数,
同样时间复杂度的流程,在实际运行中就一样好吗?
当然不是,这个时候还要比拼常数时间,简称拼常数项。
常数项比拼方式
放弃理论分析,直接生成随机大样本直接测。
为什么不去理论分析?
不是不能纯分析,而是没有必要。因为不同常数时间的操作,虽然都是固定时间,但还是有快慢之分。
如果纯理论分析,往往需要非常多的分析过程。都已经到了具体细节的程度,莫不如交给实验数据好了。
//R L 下标很大的时候 R+L 溢出
//mid= L +((R-L)>>1) 位运算就是比加减乘除快
// n * 2 + 1 -> (n << 1 ) | 1
二分不一定要有序,只要你构建一种,可以排掉另外一边的逻辑,就可以二分。
异或
异或相异为1,相同为零
异或就是无进位相加
异或的性质
1. N^0=N,N^N=0
2.满足交换律和结合率 理解:一批数里,不管顺序,最后结果一样
a 11111102
b 2545454
c 4741215
每个位上的,1或零的奇偶数量 是固定的 奇数个1就是该位结果就是1,偶数个1就是0
交换两个数
a=甲 b=乙
-> a=a^b
b=a^b
a=a^b
注意 使用这种方法,不能用同一个值(不同的内存值),相同内存会变成0
提取最右侧的1, n&(~n +1)
2.链表
java内存泄漏
如果一个超长生命函数,里面有个函数,有个list,里面其他函数调用这个list,往里面add对象,
但是后来没有释放添加的对象,导致后面多次调用,list的空间一直增减,这样内存就爆了。
JVM释放对象
当一个对象,没有任何引用可以找到,jvm就会释放这个对象。
链表的头部,得让一个引用抓住,否则jvm就可能被释放,在
操作的过程中,就找不到这个链表了。
数组实现栈
11
两个队列实现栈。
两个栈实现队列。
图
宽度优先遍历->队列
深度优先遍历->栈
问:用栈实现宽度,队列实现深度,面试官憋坏水呢?
答:两个栈拼出一个队列,,,
任何递归,都可以改成迭代。
递归实际用的系统栈。
递归复杂度
a 子问题 调用了多少次
b 子问题的规模
d 剩下的时间复杂度
HashMap增删改查 时间复杂度都是O(1)
TreeMap增删改查 时间复杂度都是O(logN)
AVL SB 红黑 时间复杂度(logN)只是常数的差别
AVL和SB是各自具备平衡性的搜索二叉树。
选择,插入、冒泡 o(N^2),在大量浪费比较行为
求小和,
求逆序对
111
3.