蓝桥杯大纲知识点详解

大学C组

1. 枚举[1-3]

模板

1
2
3
4
// 枚举模板
for (int i = 0; i < n; i++) {
// 枚举逻辑
}

解题思路

  • 遍历所有可能的情况
  • 适用于数据规模较小的问题

题目特征

  • 数据范围小(n ≤ 20)
  • 需要穷举所有可能性

时间复杂度

  • O(n) 或 O(n^k)

例题

  1. Codeforces 1A - Theatre Square
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
static void solve() {
long n = FIO.l();
long k = FIO.l();
Map<Long,Long> m = new HashMap<>();

long minn = Integer.MAX_VALUE;
for(long i = 0 ; i < n ; i++) {
long x = FIO.l();
long t = Math.min(i + 1, n - i);
if(m.containsKey(k - x) && x * 2 != k) {
minn = Math.min(minn,Math.max(m.get(k - x),t));
}
if(m.containsKey(x)){
m.put(x, Math.min(m.get(x), t));
}
else m.put(x,t);
}
FIO.P(minn == Integer.MAX_VALUE ? -1 : minn);
}
  1. LeetCode 78 - 子集
    枚举所有子集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
int [] nums;
int n;
void dfs(int i) {
if(i == n) {
ans.add(new ArrayList<>(path));
return;
}
dfs(i + 1);
path.add(nums[i]);
dfs(i + 1);
path.remove(path.size() - 1);
}
void solve() {
n = nums.length;
dfs(0);
}
public List<List<Integer>> subsets(int[] nums) {
this.nums = nums;
solve();
return ans;
}
}

二进制枚举

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public List<List<Integer>> subsets(int[] nums) {
int n = nums.length;
List<List<Integer>> ans = new ArrayList<>();
for(int i = 0 ; i < 1<<n;i++) {
List<Integer> path = new ArrayList<>();
for(int j = 0 ; j < n ; j ++ ) {
if((i >> j & 1 )== 1) {
path.add(nums[j]);
}
}
ans.add(path);
}
return ans;
}
}
  1. Codeforces 1A - Theatre Square
1
2
3
4
5
6
static void solve() {
long n = FIO.l();
long m = FIO.l();
long a = FIO.l();
FIO.P(((m + a - 1) / a) * ((n + a - 1) / a));
}

2. 排序

(1) 冒泡排序[2]

1
2
3
4
5
6
7
8
9
10
11
// 冒泡排序模板
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
// 交换
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}

解题思路

  • 相邻元素两两比较
  • 每一轮将最大的元素冒泡到最后

题目特征

  • 需要稳定排序
  • 数据规模小(n ≤ 1000)

时间复杂度

  • O(n^2)

例题

  1. Codeforces 451B - Sort the Array
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.util.*;

public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt(); // 输入数组的大小
int[] a = new int[n];
int[] b = new int[n];

// 读取数组元素
for (int i = 0; i < n; i++) {
a[i] = scanner.nextInt();
b[i] = a[i];
}

// 使用TreeMap来存储元素及其索引
Map<Integer, Integer> mp = new HashMap<>();
Arrays.sort(b);

// 建立元素值到索引的映射
for (int i = 0; i < n; i++) {
mp.put(b[i], i);
}

// 将原数组元素替换为其在排序数组中的索引
for (int i = 0; i < n; i++) {
a[i] = mp.get(a[i]);
}

int L = -1;
for (int i = 0; i < n; i++) {
if (a[i] != i) {
L = i;
break;
}
}

int R = -1;
for (int i = n - 1; i >= 0; i--) {
if (a[i] != i) {
R = i;
break;
}
}

if (L == -1 || R == -1) {
System.out.println("yes");
System.out.println(1 + " " + 1);
} else {
// 反转子数组
reverse(a, L, R);
boolean ok = true;
for (int i = 0; i < n; i++) {
if (a[i] != i) {
ok = false;
break;
}
}
if (ok) {
System.out.println("yes");
System.out.println((L + 1) + " " + (R + 1));
} else {
System.out.println("no");
}
}
}

// 反转数组的辅助方法
private static void reverse(int[] a, int L, int R) {
while (L < R) {
int temp = a[L];
a[L] = a[R];
a[R] = temp;
L++;
R--;
}
}
}

(2) 选择排序[3]

1
2
3
4
5
6
7
8
9
10
11
12
13
// 选择排序模板
for (int i = 0; i < n-1; i++) {
int minIndex = i;
for (int j = i+1; j < n; j++) {
if (arr[j] < arr[minIndex]) {
minIndex = j;
}
}
// 交换
int temp = arr[minIndex];
arr[minIndex] = arr[i];
arr[i] = temp;
}

解题思路

  • 每次找到未排序部分的最小元素
  • 将其放到已排序部分的末尾

题目特征

  • 需要不稳定排序
  • 数据规模小(n ≤ 1000)

时间复杂度

  • O(n^2)

(3) 插入排序[3]

1
2
3
4
5
6
7
8
9
10
// 插入排序模板
for (int i = 1; i < n; i++) {
int key = arr[i];
int j = i-1;
while (j >= 0 && arr[j] > key) {
arr[j+1] = arr[j];
j--;
}
arr[j+1] = key;
}

解题思路

  • 将元素插入到已排序部分的适当位置
  • 适用于部分有序的数据

题目特征

  • 需要稳定排序
  • 数据规模小(n ≤ 1000)

时间复杂度

  • O(n^2)

3. 搜索(bfs, dfs)[1-5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// BFS模板
Queue<Node> queue = new LinkedList<>();
queue.offer(startNode);
visited[start] = true;

while (!queue.isEmpty()) {
Node current = queue.poll();
// 处理当前节点

for (Node neighbor : current.neighbors) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}

解题思路

  • 使用队列实现
  • 按层次遍历图或树
  • 适合寻找最短路径问题

题目特征

  • 需要寻找最短路径或最少步骤
  • 图或树的遍历问题

时间复杂度

  • O(V+E) (V是顶点数,E是边数)

例题

  1. LeetCode 200 - 岛屿数量
  2. Codeforces 580C - Kefa and Park

(2) DFS模板

1
2
3
4
5
6
7
8
9
10
11
// DFS模板
void dfs(Node node) {
visited[node] = true;
// 处理当前节点

for (Node neighbor : node.neighbors) {
if (!visited[neighbor]) {
dfs(neighbor);
}
}
}

解题思路

  • 使用递归或栈实现
  • 深度优先遍历图或树
  • 适合寻找所有可能解的问题

题目特征

  • 需要遍历所有可能路径
  • 排列组合问题
  • 回溯问题

时间复杂度

  • O(V+E) (V是顶点数,E是边数)

4. 贪心[1-5]

1
2
3
4
5
6
7
8
9
10
11
// 贪心算法模板
while (问题未解决) {
// 选择当前最优解
Solution best = selectBestOption();

// 应用最优解
applySolution(best);

// 更新问题状态
updateProblemState();
}

解题思路

  • 每一步选择当前最优解
  • 不回溯,不重新考虑之前的选择
  • 需要证明贪心选择的正确性

题目特征

  • 问题具有最优子结构
  • 贪心选择能得到全局最优解
  • 常见于区间调度、背包问题等

时间复杂度

  • 通常为O(n)或O(nlogn)

例题

  1. LeetCode 455 - 分发饼干
  2. Codeforces 1353B - Two Arrays And Swaps

5. 模拟[1-3]

1
2
3
4
5
6
7
8
9
10
// 模拟算法模板
while (条件满足) {
// 按照题目要求逐步执行
step1();
step2();
// ...

// 更新状态
updateState();
}

解题思路

  • 严格按照题目描述实现
  • 逐步模拟题目描述的过程
  • 注意边界条件和特殊情况

题目特征

  • 题目描述详细且步骤明确
  • 需要精确实现特定规则
  • 常见于游戏规则模拟、物理过程模拟等

时间复杂度

  • 通常为O(n)或O(n^2)

例题

  1. 蓝桥杯练习系统 - 模拟题
  2. LeetCode 54 - 螺旋矩阵
  3. Codeforces 4A - Watermelon

6. 二分[2-5]

1
2
3
4
5
6
7
8
9
10
11
12
13
// 二分查找模板
int left = 0, right = n - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1; // 未找到

解题思路

  • 适用于有序数组的查找
  • 每次将搜索范围缩小一半
  • 注意边界条件的处理

题目特征

  • 数据已排序或可以排序
  • 需要高效查找(优于O(n))
  • 常见于查找特定值或满足条件的极值

时间复杂度

  • O(logn)

例题

  1. LeetCode 704 - 二分查找
  2. Codeforces 812C - Sagheer and Nubian Market

7. DP(普通一维问题)[3-5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 一维DP模板
int[] dp = new int[n];
// 初始化
for (int i = 0; i < n; i++) {
dp[i] = initialValue;
}

// 状态转移
for (int i = 1; i < n; i++) {
dp[i] = Math.max(dp[i-1] + nums[i], nums[i]); // 示例状态转移方程
}

// 结果
return Arrays.stream(dp).max().getAsInt();

解题思路

  • 定义状态dp[i]表示问题在i处的解
  • 找出状态转移方程
  • 初始化边界条件
  • 按顺序计算dp数组

题目特征

  • 问题可以分解为子问题
  • 子问题之间有重叠
  • 有最优子结构性质

时间复杂度

  • 通常为O(n)

例题

  1. 蓝桥杯练习系统 - 动态规划题
  2. LeetCode 53 - 最大子数组和
  3. Codeforces 580A - Kefa and First Steps

8. 高精度[1-5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 高精度加法模板
public static String add(String num1, String num2) {
StringBuilder sb = new StringBuilder();
int carry = 0;
int i = num1.length() - 1, j = num2.length() - 1;

while (i >= 0 || j >= 0 || carry != 0) {
int x = i >= 0 ? num1.charAt(i--) - '0' : 0;
int y = j >= 0 ? num2.charAt(j--) - '0' : 0;
int sum = x + y + carry;
sb.append(sum % 10);
carry = sum / 10;
}
return sb.reverse().toString();
}

解题思路

  • 使用字符串或数组表示大数
  • 模拟手工计算过程
  • 注意进位处理

题目特征

  • 数字超出基本数据类型范围
  • 需要精确计算
  • 常见于大数加减乘除运算

时间复杂度

  • O(n) 其中n为数字的位数

例题

  1. 蓝桥杯练习系统 - 高精度计算题
  2. LeetCode 415 - 字符串相加
  3. Codeforces 1016A - Death Note

9. 数据结构

1
2
3
4
5
6
// 栈模板
Stack<Integer> stack = new Stack<>();
stack.push(1); // 入栈
int top = stack.peek(); // 获取栈顶元素
int pop = stack.pop(); // 出栈
boolean isEmpty = stack.isEmpty(); // 判断栈是否为空

解题思路

  • 后进先出(LIFO)的数据结构
  • 适用于需要回溯的场景
  • 常用于括号匹配、表达式求值等问题

题目特征

  • 需要记录访问顺序
  • 需要撤销操作
  • 需要匹配对称结构

时间复杂度

  • 基本操作均为O(1)

例题

  1. 蓝桥杯练习系统 - 数据结构题
  2. LeetCode 20 - 有效的括号
  3. Codeforces 546A - Soldier and Bananas

(2) 队列[2-5]

1
2
3
4
5
6
// 队列模板
Queue<Integer> queue = new LinkedList<>();
queue.offer(1); // 入队
int front = queue.peek(); // 获取队首元素
int poll = queue.poll(); // 出队
boolean isEmpty = queue.isEmpty(); // 判断队列是否为空

解题思路

  • 先进先出(FIFO)的数据结构
  • 适用于需要按顺序处理的场景
  • 常用于BFS、滑动窗口等问题

题目特征

  • 需要按顺序处理元素
  • 需要维护一个处理序列
  • 需要广度优先遍历

时间复杂度

  • 基本操作均为O(1)

(3) 链表 [2-5]

1
2
3
4
5
6
7
8
9
10
11
12
13
// 链表节点定义
class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}

// 链表遍历模板
ListNode current = head;
while (current != null) {
// 处理当前节点
current = current.next;
}

解题思路

  • 动态数据结构,节点通过指针连接
  • 适用于频繁插入删除的场景
  • 常用于实现其他数据结构

题目特征

  • 需要频繁插入删除操作
  • 数据规模动态变化
  • 需要灵活的内存分配

时间复杂度

  • 遍历为O(n)
  • 插入删除为O(1)(已知位置)

10. 数学

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 判断质数模板
boolean isPrime(int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return false;
}
return true;
}

// 最大公约数模板(欧几里得算法)
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}

// 最小公倍数模板
int lcm(int a, int b) {
return a * b / gcd(a, b);
}

解题思路

  • 质数判断: 试除法
  • 最大公约数: 欧几里得算法
  • 最小公倍数: 基于最大公约数计算
  • 适用于数论相关题目

题目特征

  • 涉及质数、因数、倍数等问题
  • 需要计算公约数或公倍数
  • 常见于数学计算类题目

时间复杂度

  • 质数判断: O(√n)
  • 最大公约数: O(log(min(a,b)))

例题

  1. 蓝桥杯练习系统 - 数学题
  2. LeetCode 204 - 计数质数
  3. Codeforces 231A - Team

大学B组

11. 排序算法

(1)归并排序[4-5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Java实现模板
public class MergeSort {
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}

private static void merge(int[] arr, int left, int mid, int right) {
// 合并两个有序子数组
}
}

解题思路

  • 分治思想,先递归分解数组,再合并有序子序列
  • 典型题目:逆序对问题、外部排序
  • 时间复杂度:O(nlogn)

(2)快速排序[4-5]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// Java实现模板
public class QuickSort {
public static void quickSort(int[] arr, int low, int high) {
if (low < high) {
int pivot = partition(arr, low, high);
quickSort(arr, low, pivot - 1);
quickSort(arr, pivot + 1, high);
}
}

private static int partition(int[] arr, int low, int high) {
// 分区操作
}
}

解题思路

  • 选取基准值,将数组分为两部分
  • 典型题目:TopK问题、中位数问题
  • 时间复杂度:平均O(nlogn),最坏O(n²)

(3)桶排序[4]

1
2
3
4
5
6
7
8
// Java实现模板
public class BucketSort {
public static void bucketSort(int[] arr, int bucketSize) {
// 创建桶并分配元素
// 对每个桶进行排序
// 合并所有桶
}
}

解题思路

  • 将数据分到有限数量的桶里
  • 典型题目:数据范围已知且分布均匀的情况
  • 时间复杂度:O(n+k)

(4)堆排序[4]

1
2
3
4
5
6
7
8
// Java实现模板
public class HeapSort {
public static void heapSort(int[] arr) {
// 构建最大堆
// 交换堆顶元素与末尾元素
// 调整堆
}
}

解题思路

  • 利用堆这种数据结构设计的排序算法
  • 典型题目:优先级队列、实时求TopK
  • 时间复杂度:O(nlogn)

(5)基数排序[4~5]

1
2
3
4
5
6
7
// Java实现模板
public class RadixSort {
public static void radixSort(int[] arr) {
// 获取最大位数
// 按位进行计数排序
}
}

解题思路

  • 按照低位先排序,然后收集;再按高位排序
  • 典型题目:多关键字排序(如日期、电话号码)
  • 时间复杂度:O(n*k)

12. 搜索算法

(1)剪枝[4-6]

解题思路

  • 在搜索过程中提前排除不可能的分支
  • 典型题目:数独、八皇后问题

(2)双向BFS[5-6]

解题思路

  • 从起点和终点同时开始BFS
  • 典型题目:单词接龙、迷宫最短路径

(3)记忆化搜索[5]

1
2
3
4
5
6
7
8
9
10
11
// Java实现模板
public class Memoization {
int[] memo;

public int dfs(int state) {
if (memo[state] != 0) return memo[state];
// 计算并存储结果
memo[state] = result;
return result;
}
}

解题思路

  • 存储中间结果避免重复计算
  • 典型题目:斐波那契数列、爬楼梯问题

(4)迭代加深搜索[5-6]

解题思路

  • 结合DFS和BFS,逐步增加深度限制
  • 典型题目:IDA*算法、拼图问题

(5)启发式搜索[7]

解题思路

  • 使用估价函数指导搜索方向
  • 典型题目:A*算法、八数码问题

13. 动态规划

(1)背包DP[4-6]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 01背包模板
public class Knapsack {
public static int knapsack(int[] weights, int[] values, int capacity) {
int n = weights.length;
int[][] dp = new int[n + 1][capacity + 1];

for (int i = 1; i <= n; i++) {
for (int j = 1; j <= capacity; j++) {
if (j >= weights[i - 1]) {
dp[i][j] = Math.max(dp[i - 1][j],
dp[i - 1][j - weights[i - 1]] + values[i - 1]);
} else {
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[n][capacity];
}
}

解题思路

  • 状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i-1][j-w]+v)
  • 典型题目:01背包、完全背包、多重背包

(2)树形DP[4-6]

解题思路

  • 在树结构上进行动态规划
  • 典型题目:二叉树中的最大路径和、树的最小支配集

(3)状压DP[5-6]

解题思路

  • 使用位运算表示状态
  • 典型题目:旅行商问题、棋盘覆盖问题

(4)数位DP[5-6]

解题思路

  • 处理数字位上的计数问题
  • 典型题目:统计区间内满足条件的数字个数

(5)DP的常见优化[7]

优化方法

  • 斜率优化
  • 四边形不等式优化
  • 单调队列优化

14. 字符串算法

(1)哈希[4-5]

1
2
3
4
5
6
7
8
9
10
// 字符串哈希模板
public class StringHash {
public static long getHash(String s) {
long hash = 0;
for (char c : s.toCharArray()) {
hash = hash * 131 + c;
}
return hash;
}
}

解题思路

  • 将字符串映射为唯一数值
  • 典型题目:字符串匹配、重复子串检测

(2)KMP[4-6]

1
2
3
4
5
6
7
// KMP算法模板
public class KMP {
public static int kmp(String text, String pattern) {
// 构建next数组
// 匹配过程
}
}

解题思路

  • 利用部分匹配信息跳过不必要比较
  • 典型题目:字符串匹配、循环节问题

(3)Manacher[4-6]

1
2
3
4
5
6
7
// Manacher算法模板
public class Manacher {
public static String longestPalindrome(String s) {
// 预处理字符串
// 计算回文半径数组
}
}

解题思路

  • 线性时间求最长回文子串
  • 典型题目:最长回文子串、回文分割

15. 图论算法

(1)欧拉回路[5-7]

解题思路

  • 图中存在经过每条边恰好一次的回路
  • 典型题目:一笔画问题、邮路问题

(2)最小生成树[5-7]

1
2
3
4
5
6
7
8
// Kruskal算法模板
public class Kruskal {
public static int kruskal(int n, int[][] edges) {
// 并查集实现
// 按边权排序
// 选择边
}
}

解题思路

  • 连接所有顶点的最小权值子图
  • 典型题目:城市道路建设、网络布线

(3)单源最短路及差分约束系统[5-7]

1
2
3
4
5
6
7
// Dijkstra算法模板
public class Dijkstra {
public static int[] dijkstra(int[][] graph, int start) {
// 优先队列实现
// 松弛操作
}
}

解题思路

  • 求从一个点到其他所有点的最短路径
  • 典型题目:路由选择、差分约束系统

(4)拓扑序列[5-7]

解题思路

  • 有向无环图的线性排序
  • 典型题目:课程安排、任务调度

(5)二分图匹配[7]

1
2
3
4
5
6
// 匈牙利算法模板
public class Hungarian {
public static int maxMatch(int[][] graph) {
// 匹配过程
}
}

解题思路

  • 求二分图的最大匹配
  • 典型题目:任务分配、婚姻匹配问题

(6)图的连通性问题[7]

解题思路

  • 割点、桥、强连通分量
  • 典型题目:关键节点检测、网络脆弱性分析

(7)DFS序[5-7]

解题思路

  • 对树进行深度优先遍历得到的顺序
  • 典型题目:子树查询、树链剖分

(8)最近共同祖先[5-7]

1
2
3
4
5
6
// LCA模板
public class LCA {
public static int lca(int u, int v) {
// 倍增法实现
}
}

解题思路

  • 求树中两个节点的最近公共祖先
  • 典型题目:家族关系查询、树路径问题

16. 数学

(1) 排列组合[5-6]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 排列数模板
int permutation(int n, int k) {
int res = 1;
for (int i = 0; i < k; i++) {
res *= (n - i);
}
return res;
}

// 组合数模板(递推法)
int combination(int n, int k) {
if (k > n - k) k = n - k;
int res = 1;
for (int i = 1; i <= k; i++) {
res = res * (n - k + i) / i;
}
return res;
}

// 组合数模板(动态规划)
int[][] initCombinationDP(int maxN) {
int[][] C = new int[maxN + 1][maxN + 1];
for (int i = 0; i <= maxN; i++) {
C[i][0] = 1;
for (int j = 1; j <= i; j++) {
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
}
}
return C;
}

解题思路

  • 排列: 考虑顺序的选择
  • 组合: 不考虑顺序的选择
  • 递推法和动态规划预处理

题目特征

  • 需要计算选择方式数量
  • 涉及概率或统计问题
  • 常见于计数类题目

时间复杂度

  • 递推法: O(k)
  • 动态规划预处理: O(n^2)

(2) 二项式定理[6]

1
2
3
4
5
6
7
8
9
10
// 二项式展开模板
int[] binomialExpansion(int n) {
int[] coefficients = new int[n + 1];
coefficients[0] = 1;

for (int i = 1; i <= n; i++) {
coefficients[i] = coefficients[i - 1] * (n - i + 1) / i;
}
return coefficients;
}

解题思路

  • 计算(x+y)^n展开式的系数
  • 使用组合数计算各项系数
  • 适用于多项式展开问题

题目特征

  • 需要多项式展开
  • 涉及组合数学问题
  • 需要高效计算系数

时间复杂度

  • O(n)

17. 数据结构

(1) ST表[5-6]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// ST表模板(区间最大值查询)
class SparseTable {
int[][] st;
int[] log;

public SparseTable(int[] arr) {
int n = arr.length;
log = new int[n + 1];
for (int i = 2; i <= n; i++) {
log[i] = log[i / 2] + 1;
}

int k = log[n] + 1;
st = new int[n][k];

for (int i = 0; i < n; i++) {
st[i][0] = arr[i];
}

for (int j = 1; j < k; j++) {
for (int i = 0; i + (1 << j) <= n; i++) {
st[i][j] = Math.max(st[i][j - 1], st[i + (1 << (j - 1))][j - 1]);
}
}
}

public int query(int l, int r) {
int j = log[r - l + 1];
return Math.max(st[l][j], st[r - (1 << j) + 1][j]);
}
}

解题思路

  • 预处理区间最值
  • 支持O(1)查询
  • 适用于静态区间查询

题目特征

  • 需要频繁查询区间最值
  • 数据静态不修改
  • 查询次数远大于预处理时间

时间复杂度

  • 预处理O(nlogn)
  • 查询O(1)

(2) 堆[5-6]

1
2
3
4
5
6
7
8
9
10
11
// 堆模板(优先队列实现)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());

// 自定义比较器
PriorityQueue<Item> customHeap = new PriorityQueue<>((a, b) -> a.priority - b.priority);

// 堆操作
heap.offer(1); // 插入
int top = heap.peek(); // 获取堆顶
int pop = heap.poll(); // 弹出堆顶

解题思路

  • 完全二叉树结构
  • 父节点优于子节点
  • 高效获取极值

题目特征

  • 需要频繁获取极值
  • 需要动态维护有序集合
  • 常见于调度问题

时间复杂度

  • 插入O(logn)
  • 获取极值O(1)
  • 删除极值O(logn)

(3) 树状数组[5-6]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 树状数组模板
class FenwickTree {
int[] tree;

public FenwickTree(int size) {
tree = new int[size + 1];
}

public void update(int index, int delta) {
while (index < tree.length) {
tree[index] += delta;
index += index & -index;
}
}

public int query(int index) {
int sum = 0;
while (index > 0) {
sum += tree[index];
index -= index & -index;
}
return sum;
}
}

解题思路

  • 高效维护前缀和
  • 支持单点更新和区间查询
  • 二进制索引技术

题目特征

  • 需要频繁更新和查询前缀和
  • 数据动态变化
  • 需要高效统计

时间复杂度

  • 更新O(logn)
  • 查询O(logn)

研究生及大学A组

19. 字符串

(1) AC自动机[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// AC自动机模板
class ACNode {
ACNode[] children = new ACNode[26];
ACNode fail;
List<Integer> output;
}

class ACAutomaton {
ACNode root;

void buildTrie(String[] patterns) {
root = new ACNode();
for (int i = 0; i < patterns.length; i++) {
ACNode current = root;
for (char c : patterns[i].toCharArray()) {
int index = c - 'a';
if (current.children[index] == null) {
current.children[index] = new ACNode();
}
current = current.children[index];
}
if (current.output == null) {
current.output = new ArrayList<>();
}
current.output.add(i);
}
}

void buildFailureLinks() {
Queue<ACNode> queue = new LinkedList<>();
root.fail = null;
queue.offer(root);

while (!queue.isEmpty()) {
ACNode current = queue.poll();

for (int i = 0; i < 26; i++) {
if (current.children[i] != null) {
ACNode child = current.children[i];
ACNode failNode = current.fail;

while (failNode != null && failNode.children[i] == null) {
failNode = failNode.fail;
}

child.fail = (failNode == null) ? root : failNode.children[i];

if (child.fail.output != null) {
if (child.output == null) {
child.output = new ArrayList<>();
}
child.output.addAll(child.fail.output);
}

queue.offer(child);
}
}
}
}

List<Integer> search(String text) {
List<Integer> result = new ArrayList<>();
ACNode current = root;

for (char c : text.toCharArray()) {
int index = c - 'a';
while (current != null && current.children[index] == null) {
current = current.fail;
}
current = (current == null) ? root : current.children[index];

if (current.output != null) {
result.addAll(current.output);
}
}
return result;
}
}

解题思路

  • 结合Trie树和KMP算法
  • 构建失败指针实现高效多模式匹配
  • 适用于大量模式串的匹配问题

题目特征

  • 需要同时匹配多个模式串
  • 文本串较长
  • 模式串有公共前缀

时间复杂度

  • 构建O(Σ|P|)
  • 匹配O(|T| + m)

(2) 拓展kmp[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 拓展kmp模板
int[] extendKMP(String s, String t) {
int n = s.length(), m = t.length();
int[] next = new int[m];
int[] extend = new int[n];

// 计算next数组
next[0] = m;
int a = 0, p = 0;
for (int i = 1; i < m; i++) {
if (i >= p || i + next[i - a] >= p) {
if (i >= p) p = i;
while (p < m && t.charAt(p) == t.charAt(p - i)) p++;
next[i] = p - i;
a = i;
} else {
next[i] = next[i - a];
}
}

// 计算extend数组
a = p = 0;
for (int i = 0; i < n; i++) {
if (i >= p || i + next[i - a] >= p) {
if (i >= p) p = i;
while (p < n && p - i < m && s.charAt(p) == t.charAt(p - i)) p++;
extend[i] = p - i;
a = i;
} else {
extend[i] = next[i - a];
}
}
return extend;
}

解题思路

  • 计算字符串每个位置的最长公共前缀
  • 利用已匹配信息优化计算
  • 适用于字符串匹配和比较问题

题目特征

  • 需要比较字符串的所有后缀
  • 需要高效计算最长公共前缀
  • 常见于字符串匹配问题

时间复杂度

  • O(n+m)

22. 字符串匹配算法

(1) KMP算法[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// KMP算法模板
int kmp(String text, String pattern) {
int n = text.length(), m = pattern.length();
int[] lps = computeLPS(pattern);

int i = 0, j = 0;
while (i < n) {
if (text.charAt(i) == pattern.charAt(j)) {
i++;
j++;
if (j == m) {
return i - j; // 匹配成功
}
} else {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return -1; // 未匹配
}

int[] computeLPS(String pattern) {
int m = pattern.length();
int[] lps = new int[m];
int len = 0, i = 1;

while (i < m) {
if (pattern.charAt(i) == pattern.charAt(len)) {
lps[i++] = ++len;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i++] = 0;
}
}
}
return lps;
}

解题思路

  • 预处理模式串生成部分匹配表
  • 利用已匹配信息避免重复比较
  • 线性时间复杂度

题目特征

  • 需要高效字符串匹配
  • 模式串有重复子串
  • 适用于大文本搜索

时间复杂度

  • O(n+m)

(2) Trie树[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// Trie树模板
class Trie {
class TrieNode {
TrieNode[] children = new TrieNode[26];
boolean isEnd;
}

private TrieNode root;

public Trie() {
root = new TrieNode();
}

public void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) {
node.children[idx] = new TrieNode();
}
node = node.children[idx];
}
node.isEnd = true;
}

public boolean search(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) {
return false;
}
node = node.children[idx];
}
return node.isEnd;
}
}

解题思路

  • 前缀树数据结构
  • 高效字符串存储和检索
  • 支持前缀匹配

题目特征

  • 需要处理大量字符串
  • 需要前缀匹配或自动补全
  • 适用于字典类问题

时间复杂度

  • 插入和查询均为O(L)

23. 高级图论算法

(1) 最小生成树[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Kruskal算法模板
int kruskal(int n, int[][] edges) {
Arrays.sort(edges, (a, b) -> a[2] - b[2]);
int[] parent = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;

int res = 0, count = 0;
for (int[] edge : edges) {
int u = edge[0], v = edge[1], w = edge[2];
int rootU = find(parent, u);
int rootV = find(parent, v);
if (rootU != rootV) {
parent[rootU] = rootV;
res += w;
count++;
if (count == n - 1) break;
}
}
return res;
}

int find(int[] parent, int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}

解题思路

  • 贪心选择最小权边
  • 使用并查集检测环
  • 适用于连通图

题目特征

  • 需要连接所有节点
  • 要求总权重最小
  • 适用于网络设计问题

时间复杂度

  • O(ElogE)

(2) 拓扑排序[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 拓扑排序模板
List<Integer> topologicalSort(int n, int[][] edges) {
List<List<Integer>> graph = new ArrayList<>();
int[] inDegree = new int[n];
for (int i = 0; i < n; i++) graph.add(new ArrayList<>());

for (int[] edge : edges) {
graph.get(edge[0]).add(edge[1]);
inDegree[edge[1]]++;
}

Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < n; i++) {
if (inDegree[i] == 0) queue.offer(i);
}

List<Integer> res = new ArrayList<>();
while (!queue.isEmpty()) {
int u = queue.poll();
res.add(u);
for (int v : graph.get(u)) {
if (--inDegree[v] == 0) {
queue.offer(v);
}
}
}
return res.size() == n ? res : null; // 存在环则返回null
}

解题思路

  • 计算节点入度
  • 从入度为0的节点开始处理
  • 适用于有向无环图

题目特征

  • 需要处理依赖关系
  • 检测有向图是否有环
  • 适用于任务调度问题

时间复杂度

  • O(V+E)

24. 数论算法

(1) 快速幂[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
// 快速幂模板
long fastPow(long base, long power, long mod) {
long result = 1;
while (power > 0) {
if ((power & 1) == 1) {
result = (result * base) % mod;
}
base = (base * base) % mod;
power >>= 1;
}
return result;
}

解题思路

  • 利用二进制分解指数
  • 通过平方减少乘法次数
  • 适用于大数幂运算

题目特征

  • 需要计算大数幂
  • 需要取模运算
  • 常见于加密算法等问题

时间复杂度

  • O(log n)

(2) 欧拉筛法[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 欧拉筛法模板
List<Integer> eulerSieve(int n) {
boolean[] isPrime = new boolean[n + 1];
Arrays.fill(isPrime, true);
List<Integer> primes = new ArrayList<>();

for (int i = 2; i <= n; i++) {
if (isPrime[i]) {
primes.add(i);
}
for (int j = 0; j < primes.size() && i * primes.get(j) <= n; j++) {
isPrime[i * primes.get(j)] = false;
if (i % primes.get(j) == 0) {
break;
}
}
}
return primes;
}

解题思路

  • 线性筛法求素数
  • 每个合数只被最小质因数筛除
  • 适用于大规模素数筛选

题目特征

  • 需要高效生成素数表
  • 需要处理大范围素数问题
  • 适用于数论相关问题

时间复杂度

  • O(n)

25. 高级搜索技巧

(1) 双向BFS[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 双向BFS模板
int bidirectionalBFS(Node start, Node end) {
Set<Node> visitedFromStart = new HashSet<>();
Set<Node> visitedFromEnd = new HashSet<>();
Queue<Node> queueFromStart = new LinkedList<>();
Queue<Node> queueFromEnd = new LinkedList<>();

queueFromStart.offer(start);
visitedFromStart.add(start);
queueFromEnd.offer(end);
visitedFromEnd.add(end);

int steps = 0;
while (!queueFromStart.isEmpty() && !queueFromEnd.isEmpty()) {
// 从起点扩展
int size = queueFromStart.size();
for (int i = 0; i < size; i++) {
Node current = queueFromStart.poll();
if (visitedFromEnd.contains(current)) {
return steps * 2;
}
for (Node neighbor : current.neighbors) {
if (!visitedFromStart.contains(neighbor)) {
visitedFromStart.add(neighbor);
queueFromStart.offer(neighbor);
}
}
}
steps++;

// 从终点扩展
size = queueFromEnd.size();
for (int i = 0; i < size; i++) {
Node current = queueFromEnd.poll();
if (visitedFromStart.contains(current)) {
return steps * 2 - 1;
}
for (Node neighbor : current.neighbors) {
if (!visitedFromEnd.contains(neighbor)) {
visitedFromEnd.add(neighbor);
queueFromEnd.offer(neighbor);
}
}
}
steps++;
}
return -1; // 无解
}

解题思路

  • 同时从起点和终点开始搜索
  • 减少搜索空间
  • 适用于已知起点和终点的最短路径问题

题目特征

  • 需要寻找两点间最短路径
  • 搜索空间较大
  • 适用于对称性问题

时间复杂度

  • O(b^(d/2))

(2) A*算法[7-8]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// A*算法模板
int aStar(Node start, Node end) {
PriorityQueue<Node> openSet = new PriorityQueue<>((a, b) ->
(a.g + a.h) - (b.g + b.h));
Set<Node> closedSet = new HashSet<>();

start.g = 0;
start.h = heuristic(start, end);
openSet.add(start);

while (!openSet.isEmpty()) {
Node current = openSet.poll();
if (current.equals(end)) {
return current.g;
}

closedSet.add(current);

for (Node neighbor : current.neighbors) {
if (closedSet.contains(neighbor)) continue;

int tentativeG = current.g + distance(current, neighbor);

if (!openSet.contains(neighbor) || tentativeG < neighbor.g) {
neighbor.g = tentativeG;
neighbor.h = heuristic(neighbor, end);
neighbor.parent = current;

if (!openSet.contains(neighbor)) {
openSet.add(neighbor);
}
}
}
}
return -1; // 无解
}

解题思路

  • 使用启发式函数指导搜索方向
  • 结合Dijkstra和贪心算法
  • 适用于有启发信息的路径规划

题目特征

  • 需要寻找最优路径
  • 有可用的启发式函数
  • 适用于地图导航等问题

时间复杂度

  • 取决于启发式函数质量