前言:1、只记录我在该视频中新学会的知识,而非视频的全部内容。2、本文插入的未标注的代码默认是视频中出现的,但有的是我自己写的,会在对应位置标注(Wen_JunRen)。3、错漏之处还请指正。
目录
主线
一、复杂度和排序算法
-
选择排序
- 思想:每一趟选出当前未排序部分中的最小值放在未排序区域的首位
-
public static void selectionSort(int[] arr) { if (arr == null || arr.length < 2) { return; } for (int i = 0; i < arr.length - 1; i++) { // 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 printOddTimesNum1(int[] arr) { int res = 0; for (int cur : arr) { res ^= cur; } System.out.println(res); }
- 数组中两种数出现了奇数次,其余数出现偶数次,找出这两个奇数次的数
public static void printOddTimesNum2(int[] arr) { int tmp = 0; for (int cur : arr) { tmp ^= cur; } // tmp = a ^ b // 因为a != b,所以tmp != 0,即tmp的二进制中必有一个位置上是1 int rightOne = tmp & (~tmp + 1); // 取出tmp中最右侧的1,保留权重 int res = 0; for (int cur : arr) { if ((cur & rightOne) == 0) { res ^= cur; } } System.out.println(res + " " + (res ^ tmp)); }
- 数组中一种数出现了奇数次,其余数出现偶数次,找出这个奇数次的数
-
二分法
- 整数数组中的一个局部最优解
- 求数组中的最大值(递归)
public static int getMax(int[] arr) { return process(arr, 0, arr.length - 1); } // 求数组中[L...R]范围上最大值 public static int process(int[] arr, int L, int R) { if (L == R) { // arr[L...R]范围上只有一个数 return arr[L]; } int mid = L + ((R - L) >> 1); // 求中点 int leftMax = process(arr, L, mid); int rightMax = process(arr, mid + 1, R); return Math.max(leftMax, rightMax); }
-
master公式:
-
归并排序
public class Code01_MergeSort { // 时间复杂度O(N*logN) 空间复杂度O(N) public static void mergeSort(int[] arr) { if (arr.length < 2) { return; } sort(arr, 0, arr.length- 1); } // 递归过程,该函数让arr在L到R的范围内有序 private static void sort(int[] arr, int L, int R) { if (L == R) { return; } int mid = L + ((R - L) >> 1); sort(arr, L, mid); sort(arr, mid, R); merge(arr, L, mid, R); } // 归并操作,把arr数组中各自有序的L~mid和mid+1~R两部分合并 public static void merge(int[] arr, int L, int mid, int R) { int[] help = new int[R - L + 1]; // 开辟一个等规模的辅助数组 int i = 0; // help[]的指针 int pl = L; // L~mid部分的指针 int pr = mid + 1; // mid+1~R部分的指针 while (pl <= mid && pr <= R) { help[i++] = arr[pl] <= arr[pr] ? arr[pl++] : arr[pr++]; } while (pl <= mid) { help[i++] = arr[pl++]; } while (pr <= R) { help[i++] = arr[pr++]; } // 归并完成,拷贝回原数组 for (i = 0; i <= R; i++) { arr[L + i] = help[i]; } } }
- 小和问题
- 描述:在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。
- 逆序对问题
- 描述:在一个数组中,如果左边的数比右边的数大,则这两个数构成一个逆序对,请打印所有的逆序对。
- 小和问题
-
快速排序
- 荷兰国旗问题一
- 描述:给定一个数组arr,和一个数num,把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)。
- 思路: 若[ i ] <= num,则[ i ]和less区的下一个数交换,less区向右扩一个位置,i++ ; 若[ i ] > num,i++。
public static void hollandFlag1(int[] arr, int num) { // less所指位置及其左侧均小于等于num int less = -1; for (int i = 0; i < arr.length; i++) { if (arr[i] <= num) { swap(arr, i, ++less); } } } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } // Wen_JunRen
- 荷兰国旗问题二
- 描述:给定一个数组arr,和一个数num,把小于等于num的数放在数组的左边,等于num的数放在中间,大于num的数放在数组的右边。要求额外空间复杂度O(1),时间复杂度O(N)。
- 思路:若[ i ] < num, [ i ]和less区的下一个数交换,less区向右扩一个位置,i++ ; 若[ i ] = num,i++。
public static void hollandFlag2(int[] arr, int num) { // less所指位置及其左边均小于num,greater所指位置及其右边均大于num int less = -1, greater = arr.length; for (int i = 0; i < greater; i++) { // 注意循环终止条件不可以写成 i < arr.length,否则等于num的区域和大于num的区域会交换位置 if (arr[i] < num) { swap(arr, i, ++less); } else if (arr[i] > num) { swap(arr, i--, --greater); } } } // Wen_JunRen
-
快速排序的实现
- 思想:从数组中随机选一个数作为枢轴(或基准),一趟排序将数组依次划分为左侧小于枢轴元素的部分less、中间等于枢轴元素的部分和右侧大于枢轴元素的部分greater。然后递归地对less和greater进行划分。
- 代码
public class Code04_QuickSort { // 时间复杂度O(N * logN) 空间复杂度O(1) public static void quickSort(int[] arr) { quickSort(arr, 0, arr.length - 1); } public static void quickSort(int[] arr, int L, int R) { if (L < R) { int[] p = partition(arr, L, R); quickSort(arr, L, p[0]); // less区 quickSort(arr, p[1], R); // greater区 } } // 将arr[L...R]划分成三部分,并返回左右边界 public static int[] partition(int[] arr, int L, int R) { // 随机选一个数作为枢纽 int pivot = arr[L + (int)(Math.random() * (R - L + 1))]; int less = L - 1, greater = R + 1; for (int i = L; i < greater; i++) { if (arr[i] < pivot) { swap(arr, i, ++less); } else if (arr[i] > pivot) { swap(arr, i--, --greater); } } return new int[] {less, greater}; } public static void swap(int[] arr, int i, int j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } // Wen_JunRen }
- 荷兰国旗问题一
- 堆排序
- 引论
- 完全二叉树存储在数组中,父子结点的下标关系:对于第i个结点,其左孩子为2*i+1,右孩子为2*i+2,父结点:( i - 1) / 2。
- 大根堆:父结点的值大于所有子孙结点的值,L[ i ] >= L[ 2i + 1]且L[ i ] >= L[2i + 2]。
- 小根堆:父结点的值小于所有子孙结点的值,L[ i ] <= L[ 2i + 1]且L[ i ] <= L[2i + 2]。
- 引论
二、链表
三、二叉树
四、图
五、前缀树和贪心算法
小操作
-
用异或运算交换两个数
// 异或运算交换a和b public static void swap(int a, int b) { a = a ^ b; b = a ^ b; a = a ^ b; // tips:异或运算:相同为0,不同为1;也可理解为二进制中无进位相加 // attention:使用异或运算交换两个变量的值的前提:两个变量各自拥有独立的内存空间 }
-
用对数器测试代码
- 解释:比如自己编写的排序算法,可以生成不定长、不定值的数组测试排序结果,将其与系统排序方法的结果对照。
-
生成随机数组
// 随机生成指定最大长度、最大值的整数数组 public static int[] generateRandomArray(int maxSize, int maxValue) { // Math.rando() -> [0,1)所有小数,等概率返回一个 // Math.random() * N -> [0,N)所有小数,等概率返回一个 // (int)Math.random() * N -> [0,N-1]所有整数,等概率返回一个 int[] arr = new int[(int) (Math.random() * (maxSize + 1))]; // 长度随机 for (int i = 0; i < arr.length; i++) { // 元素值随机 arr[i] = (int) (Math.random() * (maxValue + 1) - (int) (Math.random() * maxValue)); } return arr; }
-
不溢出地求两数的平均值
L < R 1、一般方法:mid = (L + R) / 2 当L和R很大时,求L+R时可能会溢出 2、不溢出的方法:mid = L + (R - L) / 2 3、简化:mid = L + ((R - L) >> 1) 右移一位相当于除2