算法模板
基础算法
gcd / lcm
java
public static int gcd(int a, int b){
return b == 0 ? a : gcd(b, a % b);
}
public static int lcm(int a, int b){
return a * b / gcd(a, b);
}
质数
- p 和 q 为两个正整数且互质,那么 px + qy 不能表示的最大正整数为 (p - 1) * (q - 1) -1
- 任何一个正整数都可以分解为有穷个质数幂之积,如 6936 = 2^3^ * 3 * 17^2^
java
// 试除法
boolean isPrime(long x){
if (x < 2) return false;
for (long i = 2; i * i <= x; i++){
if (x % i == 0) return false;
}
return true;
}
// 分解质因数
二分
java
public int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
return -1;
}
public int leftBinarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
right = mid - 1; // 锁定左侧边界,收缩右侧边界
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
if (left < 0 || left >= nums.length)
return -1;
return nums[left] == target ? left : -1;
}
public int rightBinarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
left = mid + 1; // 锁定右侧边界,收缩左侧边界
} else if (nums[mid] < target) {
left = mid + 1;
} else if (nums[mid] > target) {
right = mid - 1;
}
}
if (right < 0 || right >= nums.length) {
return -1;
}
return nums[right] == target ? right : -1;
}
public static void main(String[] args) {
int[] nums = new int[]{1, 2, 2, 2, 3};
System.out.println(new TEMP().binarySearch(nums, 2)); // 2
System.out.println(new TEMP().leftBinarySearch(nums, 2)); // 1
System.out.println(new TEMP().rightBinarySearch(nums, 2)); // 3
}
位运算
txt
求n的第k位数字: n >> k & 1
返回n的最后一位1:lowbit(n) = n & -n
双指针
数据结构
并查集
java
class UF {
// 连通分量个数
private int count;
// 存储一棵树
private int[] parent;
// 记录树的「重量」
private int[] size;
// n 为图中节点的个数
public UF(int n) {
this.count = n;
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i;
size[i] = 1;
}
}
// 将节点 p 和节点 q 连通
public void union(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
if (rootP == rootQ)
return;
// 小树接到大树下面,较平衡
if (size[rootP] > size[rootQ]) {
parent[rootQ] = rootP;
size[rootP] += size[rootQ];
} else {
parent[rootP] = rootQ;
size[rootQ] += size[rootP];
}
// 两个连通分量合并成一个连通分量
count--;
}
// 判断节点 p 和节点 q 是否连通
public boolean connected(int p, int q) {
int rootP = find(p);
int rootQ = find(q);
return rootP == rootQ;
}
// 返回节点 x 的连通分量根节点
private int find(int x) {
while (parent[x] != x) {
// 进行路径压缩
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
// 返回图中的连通分量个数
public int count() {
return count;
}
}
图
DFS & BFS
java
int ans = 0;
boolean[][] visited;
int[][] dir = {
{0, 1}, //right
{1, 0}, //down
{-1, 0}, //up
{0, -1} //left
};
public void dfs(char[][] grid, int x, int y) {
if (visited[x][y] || grid[x][y] == '0') {
return;
}
visited[x][y] = true;
for (int i = 0; i < 4; i++) {
int nextX = x + dir[i][0];
int nextY = y + dir[i][1];
if (nextX >= 0 && nextX < grid.length && nextY >= 0 && nextY < grid[0].length && !visited[nextX][nextY] && grid[nextX][nextY] == '1') {
dfs(grid, nextX, nextY);
}
}
}
public void bfs(char[][] grid, int x, int y) {
visited[x][y] = true;
LinkedList<int[]> queue = new LinkedList<>();
queue.addLast(new int[]{x, y});
while (!queue.isEmpty()) {
int[] pop = queue.pop();
for (int i = 0; i < 4; i++) {
int nextX = pop[0] + dir[i][0];
int nextY = pop[1] + dir[i][1];
if (nextX >= 0 && nextX < grid.length && nextY >= 0 && nextY < grid[0].length && !visited[nextX][nextY] && grid[nextX][nextY] == '1') {
queue.addLast(new int[]{nextX, nextY});
visited[nextX][nextY] = true;
}
}
}
}
public int numIslands(char[][] grid) {
visited = new boolean[grid.length][grid[0].length];
for (int i = 0; i < grid.length; i++) {
for (int j = 0; j < grid[0].length; j++) {
if (!visited[i][j] && grid[i][j] == '1') {
ans++;
// dfs(grid, i, j);
bfs(grid, i, j);
}
}
}
return ans;
}
拓扑排序
java
public boolean canFinish(int numCourses, int[][] prerequisites) {
int[] in = new int[numCourses];
List<List<Integer>> map = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
map.add(new ArrayList<>());
}
for (int[] prerequisite : prerequisites) {
int v = prerequisite[0];
int u = prerequisite[1];
map.get(u).add(v);
in[v]++;
}
int num = 0;
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < numCourses; i++) {
if (in[i] == 0) {
stack.push(i);
num++;
}
}
while (!stack.isEmpty()) {
int u = stack.pop();
for (Integer v : map.get(u)) {
in[v]--;
if (in[v] == 0) {
num++;
stack.push(v);
}
}
}
return num == numCourses;
}
Kruskal 最短路径算法
所谓最小生成树,就是图中若干边的集合(我们后文称这个集合为 mst
,最小生成树的英文缩写),你要保证这些边:
1、包含图中的所有节点。
2、形成的结构是树结构(即不存在环)。
3、权重和最小。
有之前题目的铺垫,前两条其实可以很容易地利用 Union-Find 算法做到,关键在于第 3 点,如何保证得到的这棵生成树是权重和最小的。
这里就用到了贪心思路:
将所有边按照权重从小到大排序,从权重最小的边开始遍历,如果这条边和 mst
中的其它边不会形成环,则这条边是最小生成树的一部分,将它加入 mst
集合;否则,这条边不是最小生成树的一部分,不要把它加入 mst
集合。
java
class Solution {
int[] father;
// 连通分量个数
int count;
int find(int u) {
while (father[u] != u) {
father[u] = father[father[u]];
u = father[u];
}
return u;
}
void union(int u1, int u2) {
u1 = find(u1);
u2 = find(u2);
if (u1 == u2) return;
father[u1] = u2;
count--;
}
boolean connected(int u1, int u2) {
return father[u1] == u2;
}
public int minimumCost(int n, int[][] connections) {
father = new int[n];
for (int i = 0; i < n; i++) {
father[i] = i;
}
count = n;
Arrays.sort(connections, Comparator.comparingInt(a -> a[2]));
int mst = 0;
for (int[] connection : connections) {
int u = connection[0] - 1;
int v = connection[1] - 1;
if (connected(u, v)) continue;
union(u, v);
mst += connection[2];
}
return count == 1 ? mst : -1;
}
}
Dijkstra 算法
计算 start 到其他节点的最短距离
java
class State {
// 图节点的 id
int id;
// 从 start 节点到当前节点的距离
int distFromStart;
State(int id, int distFromStart) {
this.id = id;
this.distFromStart = distFromStart;
}
}
// 返回节点 from 到节点 to 之间的边的权重
int weight(int from, int to);
// 输入节点 s 返回 s 的相邻节点
List<Integer> adj(int s);
// 输入一幅图和一个起点 start,计算 start 到其他节点的最短距离
int[] dijkstra(int start, List<Integer>[] graph) {
// 图中节点的个数
int V = graph.length;
// 记录最短路径的权重,你可以理解为 dp table
// 定义:distTo[i] 的值就是节点 start 到达节点 i 的最短路径权重
int[] distTo = new int[V];
// 求最小值,所以 dp table 初始化为正无穷
Arrays.fill(distTo, Integer.MAX_VALUE);
// base case,start 到 start 的最短距离就是 0
distTo[start] = 0;
// 优先级队列,distFromStart 较小的排在前面
Queue<State> pq = new PriorityQueue<>((a, b) -> {
return a.distFromStart - b.distFromStart;
});
// 从起点 start 开始进行 BFS
pq.offer(new State(start, 0));
while (!pq.isEmpty()) {
State curState = pq.poll();
int curNodeID = curState.id;
int curDistFromStart = curState.distFromStart;
if (curDistFromStart > distTo[curNodeID]) {
// 已经有一条更短的路径到达 curNode 节点了
continue;
}
// 将 curNode 的相邻节点装入队列
for (int nextNodeID : adj(curNodeID)) {
// 看看从 curNode 达到 nextNode 的距离是否会更短
int distToNextNode = distTo[curNodeID] + weight(curNodeID, nextNodeID);
if (distTo[nextNodeID] > distToNextNode) {
// 更新 dp table
distTo[nextNodeID] = distToNextNode;
// 将这个节点以及距离放入队列
pq.offer(new State(nextNodeID, distToNextNode));
}
}
}
return distTo;
}
floyd 算法
java
// 算法结束后,d[a][b]表示a到b的最短距离
void floyd()
{
// 初始化
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (i == j) d[i][j] = 0;
else d[i][j] = INF;
for (int k = 1; k <= n; k ++ )
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
}
最小栈
java
class MinStack {
private final Stack<Integer> stack;
private final Stack<Integer> minStack;
public MinStack() {
stack = new Stack<>();
minStack = new Stack<>();
}
public void push(int x) {
stack.push(x);
if (minStack.isEmpty() || x <= minStack.peek()) {
minStack.push(x);
}
}
public void pop() {
if (stack.pop().equals(minStack.peek())) {
minStack.pop();
}
}
public int top() {
return stack.peek();
}
public int getMin() {
return minStack.peek();
}
}