0x00 语言基础
Java 快速输入
public class Main {
public static void main(String[] args) throw IOException {
int a = nextInt();
double b = nextDouble();
System.out.println(a);
System.out.println(b);
}
// 快速输入
static BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
static StringTokenizer tokenizer = new StringTokenizer("");
static String next() throws IOException {
while (!tokenizer.hasMoreTokens()) {
tokenizer = new StringTokenizer(reader.readLine());
}
return tokenizer.nextToken();
}
static int nextInt() throws IOException {
return Integer.parseInt(next());
}
}
0x01 算法基础
二分
# 求非降序范围[l, r) 找下界
public int binarySearch(int[] nums, target) {
int l = 0, r = nums.length - 1;
while (l < r) {
int mid = (r - l >> 1) + l; // 防溢出
if (nums[mid] < target) l = mid + 1;
else r = mid;
// 进阶用法
// int sum = check(mid);
// if (sum < target) l = mid + 1;
// else r = mid;
}
return nums[l] == target ? l : -1 ;
}
0x02 搜索
1 DFS
① 集合划分问题
讲集合划分为n个大小相等的子集
int[] arr; // 数组
int[] cur; // 划分子集
int ave; // 平均值
int k; // k等分
public boolean divide(int[] arr, int k) {
this.arr = arr;
this.k = k;
// 判断是否可以分为k等分
int sum = Arrays.stream(arr).sum();
ave = sum / k;
if (ave * k != sum) return false;
// 排序
Arrays.sort(arr);
cur = new int[k];
return dfs(arr.length - 1);
}
private boolean dfs(int idx) {
if (idx == -1) return true;
out:for (int i = 0; i < k; i++) {
for (int j = 0; j < i; j++) {
if (cur[j] == cur[i]) continue out;
}
int u = arr[idx];
if (cur[i] + u > ave) continue;
cur[i] += u;
if (dfs(idx - 1, cur)) return true;
cur[i] -= u;
}
return false;
}
0x03 动态规划
1 背包问题
// 01背包
public void ZeroOnePack(int v, int w) {
for (int i = 1; i <= N; i++) {
for (int j = V; j >= v; j--) {
dp[j] = Math.max(dp[j], dp[j - v] + w);
}
}
}
// 完全背包
public void CompletePack(int v, int w) {
for (int i = 1; i <= N; i++) {
for (int j = v; j <= V; j++) {
dp[j] = Math.max(dp[j], dp[j - v] + w);
}
}
}
// 多重背包
public void MultiPack(int v, int w, int s) {
if (s * v >= V) CompletePack(v, w);
for (int k = 1; k < s; s-= k, k <<= 1) ZeroOnePack(v * k, w * k)
ZeroOnePack(v * s, w * s)
}
# 混合背包
for i in range(N):
v, w, s = map(int, input().split())
if s == -1:
ZeroOnePack(v, w)
elif s == 0:
CompletePack(v, w)
else:
MultiPack(v, w, s)
# 二维费用背包
N, V, M = map(int, input().split())
f = [[0] * (M + 1) for i in range(V + 1)]
for i in range(N):
v, m, w = map(int, input().split())
for j in range(V, v - 1, -1):
for k in range(M, m - 1, -1):
f[j][k] = max(f[j][k], f[j - v][k - m] + w)
print(f[V][M])
2 最长XX子序列问题
// 最长公共子序列(LCS)
public int LCS(String text1, String text2) {
char[] s1 = text1.toCharArray();
char[] s2 = text2.toCharArray();
int len1 = s1.length, len2 = s2.length;
int[][] dp = new int[len1 + 1][len2 + 1];
for (int i = 1; i <= len1; i++) {
for (int j = 1; j <= len2; j++) {
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
}
}
return dp[len1][len2];
}
// 最长上升子序列
public int LIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
dp[0] = 1;
int maxv = 1;
for (int i = 1; i < n; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) dp[i] = Math.max(dp[i], dp[j] + 1);
}
maxv = Math.max(maxv, dp[i]);
}
return maxv;
}
// 最长公共上升子序列
public int LCIS(int[] a, int[] b) {
int n = a.length, m = a.length;
int[][] f = new int[n + 1][m + 1];
for (int i = 1; i <= n; i++) {
int maxv = 1;
for (int j = 1; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (b[j - 1] == a[i - 1]) f[i][j] = Math.max(f[i][j], maxv);
if (b[j - 1] < a[i - 1]) maxv = Math.max(f[i - 1][j] + 1, maxv);
}
}
int res = 0;
for (int i = 1; i <= m; i++) res = Math.max(res, f[n][i]);
return res;
}
3 线性DP
// 最短编辑距离,word1 到 word2需要变化多少次?
// 1. word1末尾增加一位
// 2. word2末尾增加一位 -- 等价于 word1末尾删除一位
// 3. word1末尾修改一位 -- 等价于 word2末尾修改一位
public int minDistance(String word1, String word2) {
char[] s1 = word1.toCharArray();
char[] s2 = word2.toCharArray();
int n = s1.length;
int m = s2.length;
// dp[i][j]表示,s1[1...i]到s2[1...j]最短编辑距离
int[][] dp = new int[n + 1][m + 1];
// 初始化,空字符串到长度位i的字符串需要操作i次
for (int i = 0; i <= n; i++) dp[i][0] = i;
for (int i = 0; i <= m; i++) dp[0][i] = i;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// s1[i] == s2[j] 不需要操作,继承dp[i - 1][j - 1]的情况即可
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
// s1[i] != s2[j]
// 三种情况:
// 1. s1 增加1位 dp[i - 1][j] + 1
// 2. s2 增加1位 dp[i][j - 1] + 1
// 3. s1 修改最后一位 dp[i - 1][j - 1] + 1
else dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
return dp[n][m];
}
// 最短编辑距离变形(只能修改word1,且不需要word1 == word2,word1包含word2即可)
public int minDistance(String word1, String word2) {
char[] s1 = word1.toCharArray();
char[] s2 = word2.toCharArray();
int n = s1.length;
int m = s2.length;
// dp[i][j]表示,s1[1...i]到s2[1...j]最短编辑距离
int[][] dp = new int[n + 1][m + 1];
// 初始化,s2为空字符串则不需要操作也包含
for (int i = 0; i <= n; i++) dp[i][0] = 0;
// 初始化,s1为空字符串则无法通过修改s1达成目标
for (int i = 0; i <= m; i++) dp[0][i] = INF;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
// s1[i] == s2[j] 不需要操作,继承dp[i - 1][j - 1]的情况即可
if (s1[i - 1] == s2[j - 1]) dp[i][j] = dp[i - 1][j - 1];
// s1[i] != s2[j]
// 只剩2种情况:
// 1. s1 修改最后一位 dp[i - 1][j - 1] + 1
// 2. 或者当位置不参与考虑,继承上一位
else dp[i][j] = Math.min(dp[i - 1][j - 1] + 1, dp[i - 1][j]);
}
}
return dp[n][m];
}
4 区间DP
public long sellingWood(int m, int n, int[][] prices) {
long[][] dp = new long[m + 1][n + 1];
for (int[] price : prices) {
int h = price[0], w = price[1], v = price[2];
dp[h][w] = v;
}
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
for (int k = 1; k < i; k++)
dp[i][j] = Math.max(dp[i][j], dp[k][j] + dp[i - k][j]);
for (int k = 1; k < j; k++)
dp[i][j] = Math.max(dp[i][j], dp[i][k] + dp[i][j - k]);
}
}
return dp[m][n];
}
5 状压DP
// 状态压缩DP(区间分割问题,nums平均分成k份)
public boolean divide(int[] nums, int k) {
// 预处理
int sum = Arrays.stream(nums).sum();
if (sum % k != 0) return false;
// 排序
Arrays.sort(nums);
int n = nums.length;
// 状态压缩,二进制表示物品选择情况,类似位图
int size = 1 << n;
int target = sum / k;
if (nums[nums.length - 1] > target) return false;
boolean[] dp = new boolean[size];
int[] curSum = new int[size];
// DP初始化
dp[0] = true;
// 枚举二进制压缩后的选择情况
for (int i = 0; i < size; i++) {
// 只处理被更新过的
if (!dp[i]) continue;
// 枚举
for (int j = 0; j < n; j++) {
// 当前物品已经被选择了
if ((i & (1 << j)) != 0) continue;
// 接下来要考虑的选择状态
int next = i | (1 << j);
// 如果选择后超出target,说明无法被放到当前容器
// 因为排序过,后面的都比他大,只能比target更大,直接break
if (curSum[i] % target + nums[j] > target) break;
// 更新dp状态
dp[next] = true;
// 更新数据
curSum[next] = curSum[i] + nums[j];
}
}
return dp[size - 1];
}
// 状态压缩DP(区间分割问题,nums分成k份,每一份去最大值,再取这些最大值中的的最小值)
public int divide(int[] nums, int k) {
int n = nums.length;
int[] sum = new int[1 << n]; // sun[i] 表示,数据选择情况为i时,和为sum[i]
// 求sum[i], sum[i] = sum[i - (1 << j)] + nums[j],递推求和
for (int i = 1; i < (1 << n); i++) {
for (int j = 0; j < n; j++) {
if ((i & (1 << j)) == 0) continue;
sum[i] = sum[i - (1 << j)] + nums[j];
break;
}
}
// dp[i][j],考虑前i个桶,考虑取数情况为j,能获取到的每组最大值之间的最小值的结果为dp[i][j]
int[][] dp = new int[k + 1][1 << n];
// 初始化,一个桶取数为i,则结果就是其和,因为全部分到一个组
for (int i = 0; i < (1 << n); i++)
dp[1][i] = sum[i];
// 从两个人开始递推
for (int i = 2; i <= k; i++) {
// 枚举每一种选择情况
for (int j = 0; j < (1 << n); j++) {
int minv = Integer.MAX_VALUE;
// 枚举j的子集
for (int s = j; s != 0; s = (s - 1) & j) {
// 取最大值,从前i-1组选j-s和s的和中选择最大值
// s被第i个人选择,j-s被前i-1选择
int val = Math.max(dp[i - 1][j - s], sum[s]);
// 转移方程
minv = Math.min(minv, val);
}
dp[i][j] = minv;
}
}
return dp[k][(1 << n) - 1];
}
0x04 字符串
1 KMP
static int[] next = new int[100005];
public static void main(String[] args) throws IOException {
char[] s = "ABCDEFG".toCharArray();
char[] p = "FG".toCharArray();
getNext(p);
System.out.println(kmp(0, s, p));
}
// 计算next数组
private static void getNext(char[] p) {
int plen = p.length;
int k = -1, j = 0;
next[0] = -1;
while (j < plen - 1) {
if (k == - 1 || p[k] == p[j]) {
++k;
++j;
next[j] = k;
} else k = next[k];
}
}
// 执行kmp算法
private static int kmp(int start, char[] s, char[] p) {
int slen = s.length, plen = p.length;
int i = start, j = 0;
while (i < slen && j < plen) {
if (j == -1 || s[i] == p[j]) {
++i;
++j;
} else j = next[j];
}
return (j == plen ? i - j : -1);
}
2 字符串hash
static int maxn = (int) (1e6 + 5);
static BigInteger base = BigInteger.valueOf(131);
static BigInteger mod = BigInteger.valueOf(2).pow(64);
static BigInteger[] power = new BigInteger[maxn];
static BigInteger[] hash1 = new BigInteger[maxn];
static BigInteger hash2 = BigInteger.valueOf(0);
// 初始化 base^n
static void init() {
power[0] = BigInteger.valueOf(1);
for (int i = 1; i <= 10002; i++) {
power[i] = power[i - 1].multiply(base);
}
}
public static void main(String[] args) throws IOException {
init();
char[] s2 = next().toCharArray();
char[] s1 = next().toCharArray();
int len1 = s1.length;
int len2 = s2.length;
// 需要访问每一个子串的hash,用数组存
hash1[0] = BigInteger.valueOf(0);
for (int i = 1; i <= len1; i++)、
// hash[i] = hash1[i - 1] * base + (s1[i - 1] - 'A' + 1))
// hash[i] = hash[i] % mod;
// base 和 mod 尽量互质,或者mod开的足够大也可以
hash1[i] = hash1[i - 1].multiply(base)
.add(BigInteger.valueOf(s1[i - 1] - 'A' + 1))
.add(mod).mod(mod);
// 只需要整个字符串的hash,直接存结果
for (int i = 1; i <= len2; i++)
hash2 = hash2.multiply(base)
.add(BigInteger.valueOf(s2[i - 1] - 'A' + 1))
.add(mod).mod(mod);
int cnt = 0;
for (int i = 0; i <= len1 - len2; i++) {
BigInteger hash = hash1[i + len2]
.subtract(hash1[i].multiply(power[len2]))
.add(mod).mod(mod);
if (hash.equals(hash2)) cnt++;
}
System.out.println(cnt);
}
3 最长回文长度 Manacher
private static int manacher(char[] cs) {
StringBuilder sb = new StringBuilder();
// 添加占位符去除长度奇偶性影响
sb.append('$');
for (char c : cs) sb.append('#').append(c);
sb.append("#@");
// 长度改变
int len = cs.length * 2 + 1;
cs = sb.toString().toCharArray();
// maxv 最右回文串有边界, pos 最右回文串对称中心
int maxv = 0, pos = 0, ans = 0;
// length[i] 以i为对称中心,最长的回文半径,如abcba 回文半径3
int[] length = new int[cs.length];
// 从1开始,因为 `abc` 已经编变成 `$#a#b#c$@`
for (int i = 1; i <= len; i++) {
// i 位于maxv左侧,取 length[j] 和,maxv - i 最值
// i 位于maxv右侧,保守估计,半径为1
length[i] = (maxv > i ? Math.min(length[2 * pos - i], maxv - i) : 1);
// 尝试中心扩展
while (cs[i - length[i]] == cs[i + length[i]]) length[i]++;
// 取最值
ans = Math.max(ans, length[i]);
// 更新最右侧回文串
if (length[i] + i > maxv) {
maxv = length[i] + i;
pos = i;
}
}
// 处理过后的回文串半径刚好为原串的半径 + 1
return ans - 1;
}
4 单词查找 Trie树
// 假设字符串由小写字母构成
static int SIZE = 10000;
static int[][] trie = new int[SIZE][26];
static int tot = 1;
// 用于判断结尾
static boolean[] end = new boolean[SIZE];
static void insert(char[] str) {
int len = str.length, p = 1;
for (int i = 0; i < len; i++) {
int ch = str[i] - 'a';
if (trie[p][ch] == 0) trie[p][ch] = ++tot;
p = trie[p][ch];
}
end[p] = true;
}
static boolean search(char[] str) {
int len = str.length, p = 1;
for (int i = 0; i < len; i++) {
p = trie[p][str[i] - 'a'];
if (p == 0) return false;
}
return end[p];
}
0x05 数学
1 GCD、LCM
// 最大公约数
int gcd(int a, int b) {
int t;
while (b) {
t = a % b;
a = b;
b = t;
}
return a;
}
// 最小公倍数
int lcm(int a, int b) { return a * b / gcd(a, b); }
2 素数
① 素数筛 O(N)
// 欧拉筛/线性筛
int size = 10000000; // 经测试一般oj最大范围
int[] prime = new int[size];
boolean[] jd = new boolean[size]; // false 素数
int findPrime(int size) {
jd[0] = jd[1] = true;
int no = 0;
for (int i = 2; i < size; i++) {
// 符合条件,存储素数
if (!jd[i]) prime[no++] = i;
// 核心代码,注意边界
for (int j = 0; j < no && prime[j] * i < size; j++) {
jd[prime[j] * i] = true;
// 如果可以整除退出
if (i % prime[j] == 0) break;
}
}
return no;
}
// 埃式筛(数据量大使用乘法容易爆内存,转用埃式)\
int size = 10000000; // 经测试一般oj最大范围
int[] prime = new int[size];
boolean[] jd = new boolean[size]; // false 素数
int findPrime(int n) {
int no = 0;
int sqrtn = (int)Math.sqrt(n + 0.5);
for (int i = 2; i <= sqrtn; i++) {
if (jd[i]) continue;
prime[no++] = i;
for (int j = i * i; j <= n; j += i)
jd[j] = true;
}
for (int i = sqrtn + 1; i <= n; i++)
if (!jd[i]) prime[++totPrimes] = i;
return no;
}
② 判断单个素数
// 朴素
boolean isPrime(int n) {
if (n <= 3) return n > 1;
int sqrt = (int)Math.sqrt(n);
for (int i = 2; i <= sqrt; i++) {
if(n % i == 0) {
return false;
}
}
return true;
}
// 数论
boolean isPrime(int num) {
if (num <= 3) return num > 1;
// 不在6的倍数两侧的一定不是质数
if (num % 6 != 1 && num % 6 != 5) return false;
int sqrt = (int) Math.sqrt(num);
for (int i = 5; i <= sqrt; i += 6) {
if (num % i == 0 || num % (i + 2) == 0) {
return false;
}
}
return true;
}
3 快速幂
long Pow(long a, long n){
long res = 1;
while (n > 0) {
if (n & 1 != 0) res *= a;
a*=a;
n>>=1;
}
return res;
}
long Mod_Pow(long a, long n, long mod){
long res = 1;
while (n > 0) {
if (n & 1 != 0) res = (res * a) % mod;
a = (a * a) % mod;
n>>=1;
}
return res;
}
0x06 图论
1 最短路
① Floyd算法 (多源)
for (int k = 0; k < n; k++)
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
map[i][j] = Math.min(map[i][j], map[i][k] + map[k][j]);
② Dijkstra算法 (无负边 单源)
public static void main(String[] args) throws IOException {
int n = nextInt(), m = nextInt(), s = nextInt();
int[][] map = new int[n + 1][n + 1];
for (int i = 0; i <= n; i++)
Arrays.fill(map[i], MAX);
for (int i = 0; i < m; i++)
map[nextInt()][nextInt()] = nextInt();
int[] dis = new int[n + 1];
Arrays.fill(dis, MAX);
boolean[] used = new boolean[n + 1];
dis[s] = 0;
for (int k = 0; k < n; k++) {
int x = -1;
for (int t = 1; t <= n; t++)
if (!used[t] && (x == -1 || dis[t] < dis[x]))
x = t;
used[x] = true;
for (int y = 1; y <= n; y++)
dis[y] = Math.min(dis[y], dis[x] + map[x][y]);
}
for (int i = 1; i <= n; i++) {
System.out.print(dis[i] + " ");
}
}
③ Dijkstra 优先队列(稠密图)
static final int MAX = Integer.MAX_VALUE / 2;
public static void main(String[] args) throws IOException {
int n = nextInt(), m = nextInt(), s = nextInt();
ArrayList<Edge>[] G = new ArrayList[n + 1];
for (int i = 1; i <= n; i++)
G[i] = new ArrayList<Edge>();
for (int i = 0; i < m; i++)
G[nextInt()].add(new Edge(nextInt(), nextInt()));
int[] dis = new int[n + 1];
Arrays.fill(dis, MAX);
PriorityQueue<Edge> que = new PriorityQueue<>((a, b) -> a.len - b.len);
que.add(new Edge(s, 0));
dis[s] = 0;
while (!que.isEmpty()) {
Edge e = que.poll();
int v = e.to;
if (dis[v] < e.len) continue;
for (int k = 0; k < G[v].size(); k++) {
Edge edge = G[v].get(k);
if (dis[edge.to] > dis[v] + edge.len) {
dis[edge.to] = dis[v] + edge.len;
que.add(new Edge(edge.to, dis[edge.to]));
}
}
}
for (int i = 1; i <= n; i++) {
System.out.print(dis[i] + " ");
}
}
static class Edge {
int to, len;
public Edge(int t, int l) {
to = t;
len = l;
}
}
④ Bellman-Ford算法 (可负权)
int INF = Integer.MAX_VALUE / 2;
public int Bellman-Ford() {
int[] dis = new int[n + 1];
Arrays.fill(dis, INF);
dis[k] = 0;
// 核心代码
int check = 1; // 优化
for (int i = 1; i <= n; i++) {
check = 1; // 优化
for (int j = 0; j < times.length; j++) {
if (dis[times[j][1]] > dis[times[j][0]] + times[j][2]) {
dis[times[j][1]] = dis[times[j][0]] + times[j][2];
check = 0;
}
}
if (check == 1) break; // 优化
}
}
⑤ SPFA算法 (Bellman-Ford 队列)
int INF = Integer.MAX_VALUE / 2;
public int Bellman-Ford() {
List<Edge>[] map = new ArrayList[n + 1];
int[] dis = new int[n + 1];
Arrays.fill(dis, INF);
dis[k] = 0;
int[] que = new int[10000];
int rear, front;
front = rear = -1;
que[++rear] = k;
// 核心代码
while (front != rear) {
int t = que[++front];
for (Edge e : map[t]) {
if (dis[e.to] > dis[t] + e.len) {
dis[e.to] = dis[t] + e.len;
que[++rear] = e.to;
}
}
}
}
class Edge {
int len, to;
public Edge(int t, int l) {
len = l;
to = t;
}
}
2 最小生成树
① Prim算法 (点基准)
static int[][] map;
static int INF = Integer.MAX_VALUE;
public static void main(String[] args) throws IOException {
int N = nextInt();
map = new int[N + 1][N + 1];
for (int i = 1; i <= N; i++) {
Arrays.fill(map[i], INF);
}
// 记录地图
for (int i = 0; i < N * (N - 1) / 2; i++) {
int x = nextInt(), y = nextInt(), v = nextInt(), t = nextInt();
map[x][y] = map[y][x] = (t == 1 ? 0 : v);
}
int[] lowcost = new int[N + 1]; // 生成树到该点最短距离
int[] mst = new int[N + 1]; // 最短距离的起点
// 核心代码
// 每个点的前一位是起点、每个点的最短距离是起点到他的距离
for (int i = 1; i <= N; i++) {
lowcost[i] = map[1][i];
}
lowcost[1] = -1;
for (int i = 1; i <= N; i++) {
// 找到最近的点
int min = INF;
int to = 0;
for (int j = 2; j <= N; j++) {
if (lowcost[j] != -1 && lowcost[j] < min) {
min = lowcost[j];
to = j;
}
}
// 将该点置于已经使用过的集合中
lowcost[to] = -1;
// 使用该点维护其他的点的最短距离
for (int j = 1; j <= N; j++) {
if (map[to][j] < lowcost[j]) {
lowcost[j] = map[to][j];
mst[j] = to;
}
}
}
}
② Kruskal算法 (边基准)
public static void main(String[] args) throws IOException {
int N = nextInt();
init(N);
// 优先队列
PriorityQueue<Edge> que = new PriorityQueue<>((a, b) -> a.len - b.len);
// 维护地图
for (int i = 0; i < N * (N - 1) / 2; i++) {
int x = nextInt(), y = nextInt(), v = nextInt(), t = nextInt();
que.add(new Edge(x, y, (t == 1 ? 0 : v)));
}
// 核心代码
int res = 0;
while (!que.isEmpty()) {
Edge e = que.poll();
if (find(e.from) == find(e.to)) continue;
res+=e.len;
merge(e.from, e.to);
}
System.out.println(res);
}
private static class Edge {
public int from, to, len;
public Edge(int f, int t, int l) {
from = f;
to = t;
len = l;
}
}
// 并查集相关操作
static int[] fa;
static int find(int x) {
while (fa[x] != x) x = fa[x] = fa[fa[x]];
return x;
}
static void merge(int a, int b) { fa[find(b)] = find(a); }
static void init(int N) {
fa = new int[N + 1];
for (int i = 1; i <= N; i++) fa[i] = i;
}
0x07 数据结构
1 树
① 遍历
void preOrder(TreeNode root) {
if (root == null) return;
System.out.print(root.value + " ");
preOrder(root.left);
preOrder(root.right);
}
void inOrder(TreeNode root) {
if (root == null) return;
inOrder(root.left);
System.out.print(root.value + " ");
inOrder(root.right);
}
void postOrder(TreeNode root) {
if (root == null) return;
postOrder(root.left);
postOrder(root.right);
System.out.print(root.value + " ");
}
void layerOrder(TreeNode root) {
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
root.layer = 0;
while (!q.isEmpty()) {
TreeNode temp = q.poll();
System.out.print(temp.value + " ");
if (temp.left != null) {
q.offer(temp.left);
temp.left.layer = temp.layer + 1;
}
if (temp.right != null) {
q.offer(temp.right);
temp.right.layer = temp.layer + 1;
}
}
}
② 重构
/* 前序和中序 */
TreeNode rebuildByPreAndIn(int preL, int preR, int inL, int inR) {
if (preL > preR) return null;
TreeNode root = new TreeNode(pre[preL]);
int index = 0;
for (int i = inL; i <= inR; i++) {
if (in[i] == pre[preL]) {
index = i;
break;
}
}
int numLen = index - inL;
root.left = rebuildByPreAndIn(preL + 1, preL + numLen, inL, index - 1);
root.right = rebuildByPreAndIn(preL + numLen + 1, preR, index + 1, inR);
return root;
}
/* 后序和中序 */
TreeNode rebuildByPostAndIn(int postL, int postR, int inL, int inR) {
if (postL > postR) return null;
TreeNode root = new TreeNode(post[postR]);
int index = 0;
for (int i = inL; i <= inR; i++) {
if (in[i] == post[postR]) {
index = i;
break;
}
}
int numLen = index - inL;
root.left = rebuildByPostAndIn(postL, postL + numLen - 1, inL, index - 1);
root.right = rebuildByPostAndIn(postL + numLen, postR - 1, index + 1, inR);
return root;
}
/* 层序和中序 */
TreeNode rebuildByLayerAndIn(Vector<Integer> layerT, int inL, int inR) {
if (layerT.isEmpty() || inL > inR) return null;
TreeNode root = new TreeNode(layerT.get(0));
int index = 0;
for (int i = inL; i <= inR; i++) {
if (in[i] == layerT.get(0)) {
index = i;
break;
}
}
Vector<Integer> layerL = new Vector<> ();
Vector<Integer> layerR = new Vector<> ();
for (int i = 1; i < layerT.size(); i++) {
boolean flag = false;
for (int j = inL; j < index; j++) {
if (layerT.get(i) == in[j]) {
flag = true;
break;
}
}
if (flag) layerL.add(layerT.get(i));
else layerR.add(layerT.get(i));
}
root.left = rebuildByLayerAndIn(layerL, inL, index - 1);
root.right = rebuildByLayerAndIn(layerR, index + 1, inR);
return root;
}
2 树状数组
int bit[];
void BIT(vector<int> list) {
bit = new int[nums.length + 1];
for (int i = 1; i < bit.length; i++) bit[i] = nums[i - 1];
for (int i = 1; i < bit.length; i++) {
int j = i + (i & -i);
if (j < bit.length) bit[j] += bit[i];
}
}
void update(int pos, int x) {
for (pos++; pos < bit.length; pos += (pos & -pos))
bit[pos] += x;
}
int getSum(int pos) {
int sum = 0;
for (pos++; pos > 0; pos -= (pos & -pos))
sum += bit[pos];
return sum;
}
3 堆(数组模拟)
int[] heap = new int[1025], // 堆
hp = new int[1025], // hp[i]=m; 堆中第i位,在原数组位于i
ph = new int[1025]; // ph[i]=m; 原数组第i位,在堆中位于m
int size; // 堆的大小
void head_swap(int a, int b) {
int tmp;
tmp = ph[hp[a]]; ph[hp[a]] = ph[hp[b]]; ph[hp[b]] = tmp;
tmp = hp[a]; hp[a] = hp[b]; hp[b] = tmp;
tmp = h[a]; h[a] = h[b]; h[b] = tmp;
}
void down(int u) { // 与子节点比较
int t = u;
if (u * 2 <= cnt && h[2 * u] < h[t]) t = 2 * u;
if (u * 2 + 1 <= cnt && h[2 * u + 1] < h[t]) t = 2 * u + 1;
if (t != u) {
head_swap(u, t);
down(t);
}
}
void up(int u) { // 与父节点比较
while (u >> 1 > 0 && h[u >> 1] > h[u]) {
head_swap(u >> 1, u);
u >>= 1;
}
}
public static void main(String[] args) {
// 小根堆
// 插入
heap[++size] = key;
ph[i] = size;
hp[size] = i;
up(size);
// 最小值
heap[1];
// 删除最小值
heap[1] = heap[size--]; down(1);
// 删除任意一个元素
heap[k] = heap[size--];
down(k);
up(k);
// 修改任意一个元素
heap[k] = x;
down(k);
up(k);
}
4 线段树
int[] node = new int[size];
int[] nums = new int[length];
int[] token = new int[size];
void build(int start, int end, int pos) {
if (start == end) {
node[pos] = nums[start];
return;
}
int mid = ((end = start) >> 1) + start;
build(start, mid, pos << 1);
build(mid + 1, end, (pos << 1) | 1);
node[pos] = node[pos << 1] + node[(pos << 1) | 1];
return;
}
int getsum(int l, int r, int s, int e, int p) {
if (l <= s && r >= e) return node[pos];
int m = ((e - s) >> 1) + s;
if (token[pos] != 0) {
node[p << 1] += token[p] * (m - s + 1);
node[(p << 1) | 1] += token[p] * (e - m);
token[p << 1] += token[p];
token[(p << 1) | 1] += token[p];
token[p] = 0;
}
int sum = 0;
if (l <= m) sum += getsum(l, r, s, m, p << 1);
if (r > m) sum += getsum(l, r, m + 1, e, (p << 1) | 1);
return sum;
}
void update(int l, int r, int c, int s, int e, int p) {
if (l <= s && e <= r) {
node[p] += (t - s + 1) * c;
token[p] += c;
return;
}
int m = ((e - s) >> 1) + s;
if (token[p] != 0 && s != e) {
node[p << 1] += token[p] * (m - s + 1);
node[(p << 1) | 1] += token[p] * (e - m);
token[p << 1] += token[p];
token[(p << 1) | 1] += token[p];
token[p] = 0;
}
if (l <= m) update(l, r, c, s, m, p << 1);
if (r > m) update(l, r, c, m + 1, e, (p << 1) | 1);
node[p] = node[p << 1] + node[(p << 1) | 1];
return;
}
// 基于求「区间和」以及对区间进行「加减」的更新操作
// 惰性标记 + 动态开点
public class SegmentTreeDynamic {
class Node {
// 左右孩子节点
Node left, right;
// 当前节点值
int val;
// 懒惰标记
int add;
}
private int N = (int) 1e9;
private Node root = new Node();
public void update(Node node, int start, int end, int l, int r, int val) {
// 找到满足要求的区间
if (l <= start && end <= r) {
// 区间节点加上更新值
// 注意:需要✖️该子树所有叶子节点
node.val += (end - start + 1) * val;
// 添加懒惰标记
node.add += val;
return;
}
int mid = (start + end) >> 1;
// 下推标记
pushDown(node, mid - start + 1, end - mid);
// [start, mid] 和 [l, r] 可能有交集,遍历左孩子区间
if (l <= mid) update(node.left, start, mid, l, r, val);
// [mid + 1, end] 和 [l, r] 可能有交集,遍历右孩子区间
if (r > mid) update(node.right, mid + 1, end, l, r, val);
// 向上更新
pushUp(node);
}
// 在区间 [start, end] 中查询区间 [l, r] 的结果,即 [l ,r] 保持不变
// 对于上面的例子,应该这样调用该函数:query(root, 0, 4, 2, 4)
public int query(Node node, int start, int end, int l, int r) {
// 区间 [l ,r] 完全包含区间 [start, end]
// 例如:[2, 4] = [2, 2] + [3, 4],当 [start, end] = [2, 2] 或者 [start, end] = [3, 4],直接返回
if (l <= start && end <= r) return node.val;
// 把当前区间 [start, end] 均分得到左右孩子的区间范围
// node 左孩子区间 [start, mid]
// node 左孩子区间 [mid + 1, end]
int mid = (start + end) >> 1, ans = 0;
// 下推标记
pushDown(node, mid - start + 1, end - mid);
// [start, mid] 和 [l, r] 可能有交集,遍历左孩子区间
if (l <= mid) ans += query(node.left, start, mid, l, r);
// [mid + 1, end] 和 [l, r] 可能有交集,遍历右孩子区间
if (r > mid) ans += query(node.right, mid + 1, end, l, r);
// ans 把左右子树的结果都累加起来了,与树的后续遍历同理
return ans;
}
private void pushUp(Node node) {
node.val = node.left.val + node.right.val;
}
private void pushDown(Node node, int leftNum, int rightNum) {
// 动态开点
if (node.left == null) node.left = new Node();
if (node.right == null) node.right = new Node();
// 如果 add 为 0,表示没有标记
if (node.add == 0) return ;
// 当前节点加上标记值
// 注意:需要✖️该子树所有叶子节点
node.left.val += node.add * leftNum;
node.right.val += node.add * rightNum;
// 把标记下推给孩子节点
node.left.add = node.add;
node.right.add = node.add;
// 取消当前节点标记
node.add = 0;
}
}
\