时间复杂度
- 要求:只要高阶项,不要低阶项
- 常数操作:操作花费的时间和数据量无关,比如数组寻址,直接利用偏移量找到对应元素的位置;
- 非常数操作:比如list(链表);查找元素需要遍历链表,元素查找时间和元素对应的位置有关;时间复杂度为O(n)
- 常数时间操作:两个数相加(+) 相减(-) 左移(<<) 右移(>>) 或运算(|) 与运算(&) 异或运算(^)
例子 选择排序 引出时间复杂度
package class01;import java.util.Arrays;public class Code01_SelectionSort {public static void selectionSort(int[] arr) {if (arr == null || arr.length < 2) {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;}// for testpublic static void comparator(int[] arr) {Arrays.sort(arr);}// for testpublic static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());}return arr;}// for testpublic static int[] copyArray(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}// for testpublic static boolean isEqual(int[] arr1, int[] arr2) {if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {return false;}if (arr1 == null && arr2 == null) {return true;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}// for testpublic static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}// for testpublic static void main(String[] args) {int testTime = 500000;int maxSize = 100;int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {int[] arr1 = generateRandomArray(maxSize, maxValue);int[] arr2 = copyArray(arr1);selectionSort(arr1);comparator(arr2);if (!isEqual(arr1, arr2)) {succeed = false;printArray(arr1);printArray(arr2);break;}}System.out.println(succeed ? "Nice!" : "Fucking fucked!");int[] arr = generateRandomArray(maxSize, maxValue);printArray(arr);selectionSort(arr);printArray(arr);}}
- 选择排序:先从[0->N-1]范围内找最小值,对于每个位置上元素的操作时间为c;再从[1->N-1]、[2->N-1]、[3->N-1]等,所以一共需要花费时间为((N)+(N-1)+(N-2)+ + (1))*c = (aN^2 + b*N + k)*c = acN^2 + bcN + kc 只要高阶项,不要低阶项,也不要高阶项的系数,时间复杂度为N^2,即O(N^2)
- 一个操作如果和样本数据量没有关系,每次都是固定时间内完成的操作,叫做常数操作
- 表达式中,只要高阶项,不要低阶项,也不要高阶项的系数,剩下的部分如果是f(N),那么时间复杂度就是O(f(N))
- 评价一个算法的好坏,先看时间复杂度指标,然后分析不同数据样本下的实际运行时间,也就是“常数项时间”
冒泡排序
- 最差情况 每个位置上元素都需要移动
package class01;import java.util.Arrays;public class Code02_BubbleSort {public static void bubbleSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int e = arr.length - 1; e > 0; e--) {for (int i = 0; i < e; i++) {if (arr[i] > arr[i + 1]) {swap(arr, i, i + 1);}}}}public static void swap(int[] arr, int i, int j) {arr[i] = arr[i] ^ arr[j];arr[j] = arr[i] ^ arr[j];arr[i] = arr[i] ^ arr[j];}// for testpublic static void comparator(int[] arr) {Arrays.sort(arr);}// for testpublic static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());}return arr;}// for testpublic static int[] copyArray(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}// for testpublic static boolean isEqual(int[] arr1, int[] arr2) {if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {return false;}if (arr1 == null && arr2 == null) {return true;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}// for testpublic static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}// for testpublic static void main(String[] args) {int testTime = 500000;int maxSize = 100;int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {int[] arr1 = generateRandomArray(maxSize, maxValue);int[] arr2 = copyArray(arr1);bubbleSort(arr1);comparator(arr2);if (!isEqual(arr1, arr2)) {succeed = false;break;}}System.out.println(succeed ? "Nice!" : "Fucking fucked!");int[] arr = generateRandomArray(maxSize, maxValue);printArray(arr);bubbleSort(arr);printArray(arr);}}
插入排序
- 时间复杂度 按照最差情况计算
- 插入排序 和数据情况有关,因此相较于冒泡排序和选择排序 会好
package class01;import java.util.Arrays;public class Code03_InsertionSort {public static void insertionSort(int[] arr) {if (arr == null || arr.length < 2) {return;}for (int i = 1; i < arr.length; i++) {for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {swap(arr, j, j + 1);}}}public static void swap(int[] arr, int i, int j) {arr[i] = arr[i] ^ arr[j];arr[j] = arr[i] ^ arr[j];arr[i] = arr[i] ^ arr[j];}// for testpublic static void comparator(int[] arr) {Arrays.sort(arr);}// for testpublic static int[] generateRandomArray(int maxSize, int maxValue) {int[] arr = new int[(int) ((maxSize + 1) * Math.random())];for (int i = 0; i < arr.length; i++) {arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());}return arr;}// for testpublic static int[] copyArray(int[] arr) {if (arr == null) {return null;}int[] res = new int[arr.length];for (int i = 0; i < arr.length; i++) {res[i] = arr[i];}return res;}// for testpublic static boolean isEqual(int[] arr1, int[] arr2) {if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {return false;}if (arr1 == null && arr2 == null) {return true;}if (arr1.length != arr2.length) {return false;}for (int i = 0; i < arr1.length; i++) {if (arr1[i] != arr2[i]) {return false;}}return true;}// for testpublic static void printArray(int[] arr) {if (arr == null) {return;}for (int i = 0; i < arr.length; i++) {System.out.print(arr[i] + " ");}System.out.println();}// for testpublic static void main(String[] args) {int testTime = 500000;int maxSize = 100;int maxValue = 100;boolean succeed = true;for (int i = 0; i < testTime; i++) {int[] arr1 = generateRandomArray(maxSize, maxValue);int[] arr2 = copyArray(arr1);insertionSort(arr1);comparator(arr2);if (!isEqual(arr1, arr2)) {succeed = false;break;}}System.out.println(succeed ? "Nice!" : "Fucking fucked!");int[] arr = generateRandomArray(maxSize, maxValue);printArray(arr);insertionSort(arr);printArray(arr);}}
二分查找
- 每次都是找中点,比较数据,因此时间复杂度是 O(log2N)
在一个有序数组中,找某个数是否存在?二分查找
package com.example.algorithm.demo.class1;import java.util.Arrays;public class Code04_BSExist {public static boolean exist(int[] sortedArr,int num){if (sortedArr == null || sortedArr.length == 0){return false;}int L = 0;int R = sortedArr.length - 1;int mid = 0;while (L < R){mid = L +((R - L) >> 1);if (sortedArr[mid] == num){return true;} else if (sortedArr[mid] > num){R = mid - 1;} else {L = mid + 1;}}return sortedArr[L] == num;}// for testpublic static void comparator(int[] arr) {Arrays.sort(arr);}// for testpublic static void printArray(int[] arr){for (int i = 0;i < arr.length;i++){System.out.print(arr[i] + " ");}}public static void main(String[] args) {int[] arr = {1,2,5,3,1,6,78,9,234,55,666,76};comparator(arr);printArray(arr);System.out.println();if (exist(arr,234)){System.out.println("存在数据!");} else {System.out.println("不存在数据!");}}
}
在一个有序数组中,找大于等于某个数最左侧的位置?(进一步演化)
- 比如 1223334444555556666667777777
package com.example.algorithm.demo.class1;import java.util.Arrays;public class Code05_BSNearLeft {//在arr上 找到满足>=value的最左位置public static int nearestIndex(int[] arr,int value){int L = 0;int R = arr.length - 1;int index = -1;while (L < R){int mid = L + ((R - L) >> 1);if (arr[mid] >= value){index = mid;R = mid - 1;} else {L = mid + 1;}}return index;}// for testpublic static void comparator(int[] arr) {Arrays.sort(arr);}// for testpublic static void printArray(int[] arr){for (int i = 0;i < arr.length;i++){System.out.print(arr[i] + " ");}}public static void main(String[] args) {int[] arr = {1,23,4,5,5,5,6,7,7,8,8,8,8,2,1,2,4,5,3,1,6,78,9,234,55,666,76};comparator(arr);printArray(arr);System.out.println();System.out.println("元素7的位置索引是"+ (nearestIndex(arr,7)+1));}
}
额外空间复杂度
-
使用辅助数组,额外空间就不是O(1)
局部最小
- 要求:无序数组,相邻不等,返回一个局部最小的位置,怎么整?
- 具体操作:1,先看0位置的元素,如果0位置小于1位置,那么直接返回1位置的元素;2,上一条件不满足,即0位置元素大于1位置的元素,则看N-1位置的元素是否是局部最小,如果是直接返回。如果不是,则表明N-2位置的元素小于N-1位置的元素。表明0-1曲线下降,N-2 到 N-1 曲线上升,表明在0->(N-1)绝对存在局部最小。数学知识
- 编码:使用二分搜索,判断局部最小;
原理
- 【0】<【1】,0是局部最小
- 【N-1】<【N-2】,N-1是局部最小
- 【i-1】<【i】<【i+1】,i是局部最小
方法
- 二分法,在0-N一定存在局部最小
- 二分不一定只能适用于有序数组,这个和题意有关。比如查找一个局部最小的数值
异或计算(无进位相加)
- 0异或N=N N异或N=0
- 满足交换律和结合律(具体操作和执行的次序无关)
完成数据的交换
- 引入第三方变量
int a = 1;
int b = 2;
int c = a;
a = b;
b = c;
- 单独自身计算,不引入第三方变量
int a = 1;
int b = 2;
a = a + b; //a = 3
b = a - b; //b = 3 - 2 = 1
a = a - b; //a = 3 - 1 = 2
- 使用异或(前提,值是一样的,但是a和b所处的内存区域是不同的,如果相同会出错)相同会导致数据抵消,二者都变成0
int a = 1;
int b = 2;
a = a 异或 b; //a = 1 异或 2,b = 2
b = a 异或 b; //a = 1 异或 2, b = 1
a = a 异或 b; //a = 1 异或 2 异或 1 = 2, b = 1即使a和b的元素都是2,也可以实现数据的交换,但是如果a和b指向相同的地址空间,会消除数据,a和b都变成0
- 错误使用
- i和j指向相同的位置,造成数据抹除
int[] arr = {4,5,3}
int i = 0;
int j = 0;arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];for(int c = 0; c < arr.length; c++){System.out.println(arr[c]);
}arr[0]=0;arr[1]=5;arr[2]=3;
1,一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数
public static void printOddTimesNum1(int[] arr){int eor = 0;for (int cur : arr){eor ^= cur;}System.out.println(eor);
}
- 只有一个是奇数项,将所有元素都异或,相同元素就会消失,只剩下奇数的元素,利用到了异或的交换律和结合律
2,一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两个数 例子
【1011,0110,0110,1011,1000,0001】
1,让所有元素异或运算,得到eor变量,这个利用相同位置上1的个数,如果是奇数个,则为1,同理,偶数个1为0
2,第一步得到的eor=1001,因为eor的末尾元素为1,则将所有末尾为1的元素进行第二次异或,得到eor’,具体是【1011,1011,0001】,则eor'=0001,eor'也为第一个奇数个元素
3,将eor和eor'进行异或,eor=1001,eor'=0001,则二者计算得到1000为第二个奇数个的元素
引出新的问题
- 上面例子中的eor=1001,我们提取最后面位置上的元素 1,是如何实现的呢?
- 例子(方法1)
01101000如何操作变成00001000呢?提取到指定位数的1,其余位数全部清零
假设 a = 01101000
1,计算 a-1,目的是打散最末尾的数字1,a-1 = 01100111
2,计算 a同或a-1 = 01100000,目的是清楚原始最右侧的1
3,计算 a异或(a同或a-1)= 00001000
-
例子(方法2)
a & (~a +1)
a = 01101000, a取反得到10010111,再 +1 得到10011111
a & (~a +1) = 01101000 & 10011111 = 00001000
获取最右边的 1
步骤
- 先将所有的元素进行异或,使用eor 得到 奇数 a 和 b 的异或,即 eor = a ^ b;
- 将eor转化为二进制,其中位置为1 的用于区别a 和 b;假设x位置上元素为1,将数组中元素x位置为1的全部进行异或得到eor‘;eor’ = a或者b
- 再将 eor和 eor‘ 异或得到另外一个元素
代码
public static void printOddTimesNum2(int[] arr){int eor = 0;for (int i=0;i<arr.length;i++){eor ^= arr[i];//eor = a ^ b//err != 0//err必然有一个位置上是1int rightOne = eor & (~eor + 1);//提取出最右侧的1int onlyOne = 0;//eor'for (int cur : arr){if((cur & rightOne) == 0){onlyOne ^= cur;}}}System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
package class01;public class Code07_EvenTimesOddTimes {public static void printOddTimesNum1(int[] arr) {int eO = 0;for (int cur : arr) {eO ^= cur;}System.out.println(eO);}public static void printOddTimesNum2(int[] arr) {int eO = 0, eOhasOne = 0;for (int curNum : arr) {eO ^= curNum;}int rightOne = eO & (~eO + 1);for (int cur : arr) {if ((cur & rightOne) != 0) {eOhasOne ^= cur;}}System.out.println(eOhasOne + " " + (eO ^ eOhasOne));}public static void main(String[] args) {int a = 5;int b = 7;a = a ^ b;b = a ^ b;a = a ^ b;System.out.println(a);System.out.println(b);int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 };printOddTimesNum1(arr1);int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 };printOddTimesNum2(arr2);}}
对数器
- 最简单的方法和优化的方法,使用大量的结果分别测试,如果结果是一致的,说明优化的方法是正确的。