基础算法
背模板加刷题
排序
快排
主要思想:分治
- 第一步:确认一个分界点,比如起点,中间点(分界点),末点
- 第二步:调整区间,使得第一个区间的数都小于等于分界点,第二个区间都大于分界点
- 递归处理左右两端
private static int[] quickSort(int[] arr, int left, int right) {// 递归终止条件,如果左边界大于等于右边界则认为递归结束if (left >= right) {return arr;}// 设定一个分界值,这里是(left + right)/ 2int p = arr[left + right >> 1];// 左右提前预留一个位置int i = left - 1;int j = right + 1;while (i < j) {// 等效于do while// 当数值小于分界值时持续遍历,直到找到第一个大于等于分界值的索引// 如果是逆序则调整两个while循环while (arr[++i] < p);while (arr[--j] > p);// 交换左右两侧不符合预期的数值if (i < j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}// 由于分界值取的是left + right >> 1,因此递归取的是left,j j + 1,rightquickSort(arr, left, j);quickSort(arr, j + 1, right);return arr;
}
归并排序
归并排序本质上就是分治!
但是跟快排的分治方法不一样
- 以整个数组的中间点划分
- 递归排序两边
- 将两个有序的数组合并
private static int[] mergeSort(int[] arr, int left, int right) {// 递归终止条件,如果左边界大于等于右边界则认为递归结束if (left >= right) {return arr;}// 设定一个分界值,这里是(left + right)/ 2int mid = left + right >> 1; // 位运算// 切割arr = mergeSort(arr, left, mid);arr = mergeSort(arr, mid + 1, right);// 归并,长度刚好是 left 到 rightint[] temp = new int[right - left + 1];int i = left;int j = mid + 1;// 用来归并的索引int k = 0;while (i <= mid && j <= right) {// 如果是逆序则调整if条件if (arr[i] <= arr[j]) {temp[k++] = arr[i++];} else {temp[k++] = arr[j++];}}// 如果其中一半已经遍历完,另一半可能还有剩余元素,直接全部拷贝过来。一般二者肯定会剩下一个出来while (i <= mid) {temp[k++] = arr[i++];}while (j <= right) {temp[k++] = arr[j++];}// 根据归并后的数组重新赋值排序后的数组for (i = left, j = 0; i <= right; i++, j++) {arr[i] = temp[j];}return arr;
}
二分
有单调性一定可以二分,但是二分不一定有单调性
整数二分
// 检查x是否满足某种性质
private static boolean check(int x) { /* ... */
} // 区间[left, right]被划分成[left, mid]和[mid + 1, right]时使用:
// 或者称之为左二分查询,查找左侧第一个满足条件的数
private static int leftBinarySearch(int[] arr, int left, int right) { while (left < right) { int mid = arr[left + right >> 1]; if (check(mid)) { right = mid; // check()判断mid是否满足性质 } else { left = mid + 1; } } return left;
} // 区间[left, right]被划分成[left, mid - 1]和[mid, right]时使用:
// 或者称之为右二分查询,查找右侧最后一个满足条件的数
private static int rightBinarySearch(int[] arr, int left, int right) { while (left < right) { int mid = arr[left + right + 1 >> 1]; if (check(mid)) { left = mid; // check()判断mid是否满足性质 } else { right = mid - 1; // 有加必有减} } return left;
}
浮点二分
// 检查x是否满足某种性质
private static boolean check(double x) { /* ... */
} // eps 表示精度,取决于题目对精度的要求,默认负六次方
private static double EPS = 1e-6;private static double floatBinarySearch(double left, double right) { while (right - left > EPS) { double mid = (left + right) / 2; if (check(mid)) { right = mid; } else { left = mid; } } return left;
}
一维前缀和
S[i] = a[1] + a[2] + … a[i]
a[l] + … + a[r] = S[r] - S[l - 1]
private static final int N = 100010;
private static final int[] a = new int[N + 5];
private static final int[] s = new int[N + 5];private static void init(int n) { for (int i = 1; i <= n; i++) { s[i] = s[i - 1] + a[i]; }
} private static int sumPrefix(int left, int right) { return s[right] - s[left - 1];
}
二维前缀和
S[i, j] = 第 i 行 j 列格子左上部分所有元素的和
以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:
S[x2, y2] - S[x1-1, y2] - S[x2, y1-1] + S[x1-1, y1-1]
private static final int N = 1010;
private static final int[][] a = new int[N + 5][N + 5];
private static final int[][] s = new int[N + 5][N + 5];private static void init(int n, int m) { for (int i = 1; i <= n; i++) { for (int j = 1; j <= m; j++) { s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + a[i][j]; } }
} private static int sumPrefix(int x1, int y1, int x2, int y2) { return s[x2][y2] - s[x2][y1 - 1] - s[x1 - 1][y2] + s[x1 - 1][y1 - 1];
}
一维差分
给区间[l, r]中的每个数加上 c:B[l] += c, B[r + 1] -= c
private static final int N = 100010;
private static final int[] a = new int[N + 5];
private static final int[] b = new int[N + 5];private static void init(int n) { for (int i = 1; i <= n; i++) { b[i] = a[i] - a[i - 1]; }
} private static void difference(int left, int right, int c) { b[left] += c; b[right + 1] -= c;
}