常用算法
快速排序
public int[] sortArray(int[] nums) {
if (nums == null || nums.length < 2) return nums;
quickSort(nums, 0, nums.length - 1);
return nums;
}
public void quickSort(int[] arr, int st, int ed) {
if (st >= ed) return ;
int mi = arr[st + ed >> 1];
int l = st - 1, r = ed + 1;
while (l < r) {
while (arr[--r] > mi) ;
while (arr[++l] < mi) ;
if (l < r) swap(arr, l, r);
}
quickSort(arr, st, r);
quickSort(arr, r + 1, ed);
}
快速选择第k大
public int findKthLargest(int[] arr, int k) {
return quickSelect(arr, 0, arr.length - 1, k);
}
int quickSelect(int[] arr, int k, int st, int ed){
if(st >= ed) return arr[st];
int l = st - 1, r = ed + 1;
int mi = arr[l + r >> 1];
while(l < r){
while(arr[++l] > mi);
while(arr[--r] < mi);
if(l < r) swap(arr, l, r);
}
int pivotPos = r - st + 1;
if(pivotPos >= k) return quickSelect(arr, st, r, k);
return quickSelect(arr, r + 1, ed, k - pivotPos);
}
归并排序
public int[] sortArray(int[] nums) {
if (nums == null || nums.length < 2) return nums;
mergeSort(nums, 0, nums.length - 1, new int[nums.length]);
return nums;
}
public void mergeSort(int[] arr, int l, int r, int[] tmp) { // tmp防止每次开数组
if (l >= r) return ;
int m = (l + r) >>> 1;
mergeSort(arr, l, m, tmp); // 以m区间划分[l, r]
mergeSort(arr, m + 1, r, tmp);
int i = l, j = m + 1; // 左右区间分别的起始点i, j
int p = 0;
while (i <= m && j <= r) tmp[p++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
while (i <= m) tmp[p++] = arr[i++];
while (j <= r) tmp[p++] = arr[j++];
p = 0;
while (l <= r) arr[l++] = tmp[p++];
}
堆排序
public int[] sortArray(int[] arr){
//1. 建堆
for (int i = arr.length / 2 - 1; i >= 0; i--) { //从第一个非叶子结点从下至上,从右至左调整结构
heapify(arr, i, arr.length);
}
//2. 调整堆结构+交换堆顶元素与末尾元素
for (int j = arr.length - 1; j > 0; j--) {
swap(arr, 0, j);
heapify(arr, 0, j); // 重新对堆进行调整
}
return arr;
}
void heapify(int[] arr, int i, int length) { // i为父节点,2i+1为左孩子,2i+2为右孩子
for (int k = 2 * i + 1; k < length; k = 2 * k + 1) { // 每次从左孩子开始
if(k + 1 < length && arr[k] < arr[k + 1]) // k指向左右孩子中的最大值
k++;
if(arr[i] < arr[k]){ // 1. 如果父节点i < 孩子中的最大值k
swap(arr, i, k); // 1.1 父结点i下沉到最大值k处, i与k交换
i = k; // 1.2 父节点i沿着孩子中的最大值k一路下沉,直到已经是大顶堆
} else break; // 2. 否则父节点i > 孩子中的最大值,已经是大顶堆
}
}
KMP
public int strStr(String str, String pattern) {
int m = str.length(), n = pattern.length();
if (n == 0) return 0; // 默认空的模式串pattern是匹配的
if (m < n) return -1; // 模式串 > 匹配串
char[] s = str.toCharArray();
char[] p = pattern.toCharArray();
int[] next = getNext(p);
int i = 0, j = 0; //
while (i < m && j < n) {
if (s[i] == p[j]) { // 1.如果当前字符匹配,指针分别后移一位
++i;
++j;
} else if (j > 0) { // 2.否则,模式串指针开始向前找最大相同前后缀
j = next[j];
} else { // 3.直到j == 0,也没有最大相同前后缀,i位置匹配失败,从匹配串下一个字符开始匹配
++i;
}
}
return j == n ? i - n : -1; // 如果模式串遍历完,则找到一个匹配位置:当前匹配串i处向前一个pattern
}
private int[] getNext(char[] pattern) {
int n = pattern.length;
int[] next = new int[n];
next[0] = -1;
// java默认next[1] = 0;
int i = 2; // 0,1处最大相同前后缀已定义好,从i=2开始
// 1.最大匹配前缀的下一个待检查字符pattern[pre]
// 2.在i-1处的匹配前后缀长度:next[i-1]==pre
int pre = 0;
while (i < n) {
// 最大匹配前缀的下一个字符pre和当前位置i的前一个字符i-1比较
if (pattern[pre] == pattern[i - 1]) { // 1. 若相等,则当前位置匹配长度next[i], 是在i-1匹配长度next[i-1]==pre的基础上+1
next[i++] = ++pre;
} else if (pre > 0) { // 2. 否则pre向前找匹配前缀
pre = next[pre];
} else { // 3. 直到pre == 0也找不到,说明当前位置匹配长度next[i]=0
// next[i++] = 0; // java数组默认是0
i++;
}
}
return next;
}
Manacher
public String longestPalindrome(String str) {
char[] s = process(str);
int len = s.length;
int R = -1; // 全局最右回文下标
int C = -1; // 全局回文中心
int max = -1;
int[] maxPalinR = new int[len]; // 每个位置的最大回文半径
for (int i = 0; i < len; ++i) {
maxPalinR[i] = i < R ? Math.min(R - i, maxPalinR[2 * C - i]) : 1;
while (i + maxPalinR[i] < len && i - maxPalinR[i] >= 0) {
if (s[i + maxPalinR[i]] == s[i - maxPalinR[i]]) {
maxPalinR[i]++;
} else break;
}
if (i + maxPalinR[i] > R) {
R = i + maxPalinR[i];
C = i;
}
max = Math.max(max, maxPalinR[i]);
}
return max - 1;
}
单源最短路径
BFS(要求权值相同)
Dijkstra = 带权BFS + FibHeap优化(要求无负权边)
public int[] dijkstra(int[][] graph, int src) {
// 针对零权边,还需要graph除了有权的可达边外,其余不可达边权值都为-inf
int n = graph.length;
int[] dist = new int[n];
Arrays.fill(dist, Integer.MAX_VALUE); // 先默认dist[i]= +inf表示src到i不可达
dist[src] = 0; // src到自身minDist = 0
// 按curDist的小顶堆
PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[1] - o2[1]);
// src到自身的curDist = 0
pq.offer(new int[]{src, 0});
while (!pq.isEmpty()) {
int[] cur = pq.poll();
int curV = cur[0]; // 当前顶点curV
int curDist = cur[1]; // src到当前顶点curV的距离curDist
if (curDist > dist[curV]) continue;
for (int i = 0; i < n; ++i) {
// 遍历当前顶点curV->邻接点i的距离,加上src->curV的距离curDist,组成src->i的距离newDist
int newDist = curDist + graph[curV][i];
if (graph[v][i] >= 0 && newDist < dist[i]) {
//取最小newDist作为src->i的最小距离
pq.offer(new int[]{i, dist[i] = newDist});
}
}
}
return dist;
}
Bellman-Ford(可以负权边, 但不能存在和为负的环)
public int[] bellmanFord(int[][] graph, int src) {
int n = graph.length;
int[] dist = new int[n];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
for (int i = 0; i < n - 1; ++i) {
for (int j = 0; j < n; ++j) {
for (int k = 0; k < n; ++k) {
dist[k] = Math.min(dist[k], dist[j] + graph[j][k]);
}
}
}
for (int j = 0; j < n; ++j) {
for (int k = 0; k < n; ++k) {
dist[k] = Math.min(dist[k], dist[j] + graph[j][k]);
}
}
return dist;
}
全源最短路径
遍历每个顶点, 分别使用单源最短路径
Floyd = DP(要求无负权回路)
public int[][] floyd(int[][] graph) {
int n = graph.length;
int[][] dist = new int[n][n];
// 或者直接在原图graph上改
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
dist[i][j] = graph[i][j];
}
}
// 建立最短路径矩阵:源点i经过中间点k到达目标j
for (int k = 0; k < n; ++k) { // 枚举中间点k
for (int i = 0; i < n; ++i) {
for (int j = 0; j < n; ++j) {
dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
return dist;
}
Johnson = 松弛遍历Dijkstra
- 建立虚拟根节点root
- bellman-ford求root到所有点距离h[]
- 更新原权重w(u, v) += (h[u] - h[v])
- 移除root, 做dijkstra