多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] # 1 时间复杂度、空间复杂度、排序、异或运算 ## 1.1 时间复杂度 - 常数时间操作: 1. 算数运算:+ - * / 2. 位运算:>>(带符号右移动)、 >>>(不带符号右移动) 、 <<、 | 、& 、^ ==带符号就是最高位补符号位,不带符号就是最高位补0== 3. 赋值操作:比较,自增,自减操作 4. 数组寻址等 > 总之,执行时间固定的操作都是常数时间的操作。反之执行时间不固定的操作,都不是常数时间的操作 - 通过基本动作的常数时间,推导时间复杂度 > 对于双层循环来说,n*(常数)+ (n-1)*(常数)+ ... + 2*(常数) + 1*(常数) => 推导出 ```math y = an^2 + bn + c ``` ==忽略掉低阶项,忽略掉常数项,忽略掉高阶项的系数,得到时间复杂度为n^2== ### 1.1.1 排序操作 #### 1.1.1.1 选择排序 ```Java package class01; import java.util.Arrays; public class Code01_SelectionSort { public static void selectionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } // 0 ~ N-1 // 1~n-1 // 2 for (int i = 0; i < arr.length - 1; i++) { // i ~ N-1 // 最小值在哪个位置上 i~n-1 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); } } public static void swap(int[] arr, int i, int j) { int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp; } // for test public static void comparator(int[] arr) { Arrays.sort(arr); } // for test public static int[] generateRandomArray(int maxSize, int maxValue) { // Math.random() [0,1) // Math.random() * N [0,N) // (int)(Math.random() * N) [0, N-1] 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 test public 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 test 对数器 public 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 test public 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 test public 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); } } ``` #### 1.1.1.2 冒泡排序 ```Java package class01; import java.util.Arrays; public class Code02_BubbleSort { public static void bubbleSort(int[] arr) { if (arr == null || arr.length < 2) { return; } // 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); } } } } // 交换arr的i和j位置上的值 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 test public static void comparator(int[] arr) { Arrays.sort(arr); } // for test public 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 test public 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 test public 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 test public 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 test public 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); } } ``` #### 1.1.1.3 插入排序 ```Java package class01; import java.util.Arrays; public class Code03_InsertionSort { public static void insertionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } // 0~0 有序的 // 0~i 想有序 for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序 for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) { swap(arr, j, j + 1); } } } // i和j是一个位置的话,会出错 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 test public static void comparator(int[] arr) { Arrays.sort(arr); } // for test public static int[] generateRandomArray(int maxSize, int maxValue) { // Math.random() -> [0,1) 所有的小数,等概率返回一个 // Math.random() * N -> [0,N) 所有小数,等概率返回一个 // (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个 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 test public 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 test public 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 test public 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 test public static void main(String[] args) { int testTime = 500000; int maxSize = 100; // 随机数组的长度0~100 int maxValue = 100;// 值:-100~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)) { // 打印arr1 // 打印arr2 succeed = false; break; } } System.out.println(succeed ? "Nice!" : "Fucking fucked!"); int[] arr = generateRandomArray(maxSize, maxValue); printArray(arr); insertionSort(arr); printArray(arr); } } ``` ==插入排序和前面两种排序的不同是在于,插入排序跟数组初始顺序有关,在初始有序的情况下,有可能时间复杂度为O(N),有可能为O(N ^2),但是我们估计时间复杂度要按照最差的情况来估计,所以插入排序的时间复杂度仍然O(N ^2)== ## 1.2 空间复杂度 申请有限几个变量,和样本量n没关系,就是空间复杂度O(1),如果要开辟一个空间数组和样本量n是一样大,用来支持我们的算法流程那么O(N)。反之用户就是要实现数组拷贝,我们开辟一个新的n大小数组用来支撑用户的需求,那么仍然是O(1) ## 1.3 常数项时间复杂度 如果两个相同时间复杂度的算法要比较性能,这个时候需要比较单个常数项时间,对能力要求较高,没有意义,不如样本量试验实际测试来比较 ## 1.4 算法最优解 我们认为最优解的考虑顺序是,先满足时间复杂度指标,再去使用较少的空间。一般来说,算法题,ACM等不会卡常数项时间 ## 1.5 常见时间复杂度 > 依次从好到坏 O(1) -> O(logN) -> O(N) -> O(N*logN) -> O(N^2) -> O(N^3) ... -> O(N!) ## 1.6 算法和数据结构脉络 1. 知道怎么算的算法 2. 知道怎么试的算法(递归) ## 1.7 认识对数器 1. 准备你想要测试的方法a 2. 实现一个复杂度不好,但是容易实现的方法b 3. 实现一个随机样本产生器 4. 把方法a和方法b跑相同的随机样本,看看得到的结果是否一样 5. 如果有一个随机样本使得对比结果不一致,打印样本进行人工干预,改对方法a和方法b 6. 当样本数量很多,测试对比依然正确,可以确定方法a已经正确 ## 1.8 认识二分法 1. 在一个有序数组中,找某个数是否存在 > 二分查找值,基于有序数组,算法复杂度为二分了多少次,O(log2N)可以写成O(logN) > 123579 ```Java package class01; 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; // L..R while (L < R) { // mid = (L+R) / 2; // L 10亿 R 18亿 // mid = L + (R - L) / 2 // N / 2 N >> 1 mid = L + ((R - L) >> 1); // mid = (L + R) / 2 if (sortedArr[mid] == num) { return true; } else if (sortedArr[mid] > num) { R = mid - 1; } else { L = mid + 1; } } return sortedArr[L] == num; } // for test public static boolean test(int[] sortedArr, int num) { for(int cur : sortedArr) { if(cur == num) { return true; } } return false; } // for test public 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; } public static void main(String[] args) { int testTime = 500000; int maxSize = 10; int maxValue = 100; boolean succeed = true; for (int i = 0; i < testTime; i++) { int[] arr = generateRandomArray(maxSize, maxValue); Arrays.sort(arr); int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); if (test(arr, value) != exist(arr, value)) { succeed = false; break; } } System.out.println(succeed ? "Nice!" : "Fucking fucked!"); } } ``` 2. 在一个有序数组中,找>=某个数最左侧的位置 > 122222333578888999999 找大于等于2最左侧的位置 ```Java package class01; 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 test public static int test(int[] arr, int value) { for (int i = 0; i < arr.length; i++) { if (arr[i] >= value) { return i; } } return -1; } // for test public 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 test public 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(); } public static void main(String[] args) { int testTime = 500000; int maxSize = 10; int maxValue = 100; boolean succeed = true; for (int i = 0; i < testTime; i++) { int[] arr = generateRandomArray(maxSize, maxValue); Arrays.sort(arr); int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); if (test(arr, value) != nearestIndex(arr, value)) { printArray(arr); System.out.println(value); System.out.println(test(arr, value)); System.out.println(nearestIndex(arr, value)); succeed = false; break; } } System.out.println(succeed ? "Nice!" : "Fucking fucked!"); } } ``` 3. 在一个有序数组中,找<=某个数最右侧的位置 ```Java package class01; import java.util.Arrays; public class Code05_BSNearRight { // 在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; L = mid + 1; } else { R = mid - 1; } } return index; } // for test public static int test(int[] arr, int value) { for (int i = arr.length - 1; i >= 0; i--) { if (arr[i] <= value) { return i; } } return -1; } // for test public 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 test public 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(); } public static void main(String[] args) { int testTime = 500000; int maxSize = 10; int maxValue = 100; boolean succeed = true; for (int i = 0; i < testTime; i++) { int[] arr = generateRandomArray(maxSize, maxValue); Arrays.sort(arr); int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random()); if (test(arr, value) != nearestIndex(arr, value)) { printArray(arr); System.out.println(value); System.out.println(test(arr, value)); System.out.println(nearestIndex(arr, value)); succeed = false; break; } } System.out.println(succeed ? "Nice!" : "Fucking fucked!"); } } ``` 4. 局部最小值问题 > 无序数组,任意两个相邻的数不相等,返回一个局部最小值 ```Java package class01; public class Code06_BSAwesome { public static int getLessIndex(int[] arr) { if (arr == null || arr.length == 0) { return -1; // no exist } if (arr.length == 1 || arr[0] < arr[1]) { return 0; } if (arr[arr.length - 1] < arr[arr.length - 2]) { return arr.length - 1; } int left = 1; int right = arr.length - 2; int mid = 0; while (left < right) { mid = (left + right) / 2; if (arr[mid] > arr[mid - 1]) { right = mid - 1; } else if (arr[mid] > arr[mid + 1]) { left = mid + 1; } else { return mid; } } return left; } } ``` ## 1.9 认识异或运算 异或运算:相同为0,不同为1 同或运算:相同为1, 不同为0,不掌握 ==上述特别不容易记住,异或运算就记成无进位相加:比如十进制6异或7,就理解为110和111按位不进位相加,得到001== 1. 所以 0^N = N , N^N = 0 2. 异或运算满足交换律和结合律,所以A异或B异或C = A异或(B异或C) = (A异或C)异或B 题目一:如何不用额外变量就交换两个数 ```shell a = x b = y两个数交换位置 a = a ^ b # 第一步操作,此时 a = x^y , b=y b = a ^ b # 第二步操作,此时 a = x^y , b = x^y^y => b = x^0 => b = x a = a ^ b # 第三步操作,此时 a = x^y^x, b = x, a=> x^x^y => a=y 三步操作,实现交换ab的值 ``` ```Java package class01; public class Test { public static void main(String[] args) { int a = 6; int b = 6; a = a ^ b; b = a ^ b; a = a ^ b; System.out.println(a); System.out.println(b); int[] arr = {3,1,100}; System.out.println(arr[0]); System.out.println(arr[2]); swap(arr, 0, 0); System.out.println(arr[0]); System.out.println(arr[2]); } public static void swap (int[] arr, int i, int j) { // arr[0] = arr[0] ^ arr[0]; arr[i] = arr[i] ^ arr[j]; arr[j] = arr[i] ^ arr[j]; arr[i] = arr[i] ^ arr[j]; } } ``` ==注意,如果a和b指向同一块内存,改方法不可行== 题目二:一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数 > [2,2,1,3,2,3,2,1,1] 数组中存在四个2,两个3,三个1,定义一个常量等于0,分别对该数组中的数遍历一遍进行异或,最后,该变量等于多少,那么奇数的值就是多少。因为异或运算满足交换和结合律 题目三:怎么把一个int类型的数,提取出最右侧的1来 > n与上(n取反加1)即可 => N & ( (~N)+1 ) 题目四:一个数组中有两种不相等的数出现了奇数次,其他数出现了偶数次,怎么找到并打印这两种数 > 定义一个常量eor = 0,分别对该数组每个数异或,最终结果为a异或b,其中a和b就是这两个奇数,由于a!=b所以a异或b不等于0,即eor的值某一位上一定为1(有可能不止一个1随便选一个例如第八位),用该位做标记对原有数组的数进行分类,那么a和b由于第八位不相同一定被分开,再定义常量eor' = 0分别对第八位为0的数异或,那么得到的值,就是a和b其中一个,由于之前eor = a异或b,那么在用eor和eor'异或,就是另外一个值。一般来说,随便找一个1我们就找最右侧的那个1,如题目三 ```Java package class01; public class Code07_EvenTimesOddTimes { // arr中,只有一种数,出现奇数次 public static void printOddTimesNum1(int[] arr) { int eor = 0; for (int i = 0; i < arr.length; i++) { eor ^= arr[i]; } System.out.println(eor); } // arr中,有两种数,出现奇数次 public static void printOddTimesNum2(int[] arr) { int eor = 0; for (int i = 0; i < arr.length; i++) { eor ^= arr[i]; } // eor = a ^ b // eor != 0 // eor必然有一个位置上是1 // 0110010000 // 0000010000 int rightOne = eor & (~eor + 1); // 提取出最右的1 int onlyOne = 0; // eor' for (int i = 0 ; i < arr.length;i++) { // arr[i] = 111100011110000 // rightOne= 000000000010000 if ((arr[i] & rightOne) != 0) { onlyOne ^= arr[i]; } } System.out.println(onlyOne + " " + (eor ^ onlyOne)); } public static int bit1counts(int N) { int count = 0; // 011011010000 // 000000010000 1 // 011011000000 // while(N != 0) { int rightOne = N & ((~N) + 1); count++; N ^= rightOne; // N -= rightOne } return count; } 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); } } ```