一、动态规划(Dynamic Programming)
算法解析
动态规划是蓝桥杯高频考点,其核心思想是将复杂问题分解为一系列相互关联的子问题,通过记录子问题的解避免重复计算,利用状态转移方程逐步推导出原问题的最优解。常见题型包括背包问题、最长递增子序列(LIS)等。在解决动态规划问题时,关键在于定义状态和状态转移方程,以及确定边界条件。
真题实战
题目:0-1 背包问题(2020 年省赛)描述:给定n
个物品,每个物品有重量w
和价值v
,背包容量为W
,求能装入背包的最大价值。每个物品只能选择放入或不放入(0-1 选择)。
fun knapsack(n: Int, W: Int, weights: IntArray, values: IntArray): Int {
// 创建二维数组dp,dp[i][j]表示考虑前i个物品,背包容量为j时的最大价值
val dp = Array(n + 1) { IntArray(W + 1) }
for (i in 1..n) { // 遍历每个物品
for (j in 0..W) { // 遍历每个背包容量
// 如果当前物品重量大于背包容量j,无法放入该物品,价值与前i-1个物品相同
if (weights[i - 1] > j) {
dp[i][j] = dp[i - 1][j]
} else {
// 否则,取不放入该物品(dp[i - 1][j])和放入该物品(dp[i - 1][j - weights[i - 1]] + values[i - 1])的较大值
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weights[i - 1]] + values[i - 1])
}
}
}
// 返回考虑完所有n个物品,背包容量为W时的最大价值
return dp[n][W]
}
二、贪心算法(Greedy Algorithms)
算法解析
贪心算法通过在每一步选择当前状态下的局部最优解,逐步构建全局最优解。适用于区间调度、哈夫曼编码等问题。但贪心算法的正确性依赖于问题的贪心选择性质,即每一步的局部最优选择不会影响最终的全局最优解,因此使用时需要谨慎证明策略的正确性。
真题实战
题目:区间覆盖(2019 年国赛)描述:给定n
个区间,每个区间由起始点和结束点表示,目标是选择最少的区间,使其能够覆盖整个数轴上的目标区间[targetStart, targetEnd]
。
data class Interval(val start: Int, val end: Int)
fun intervalCover(intervals: List<Interval>, targetStart: Int, targetEnd: Int): Int {
// 按照区间起始点从小到大排序
intervals.sortBy { it.start }
var count = 0 // 记录选择的区间数量
var currentEnd = targetStart // 当前已覆盖的最右边界
var i = 0 // 区间列表的索引
while (currentEnd < targetEnd) { // 当还未覆盖到目标终点时
var farthest = currentEnd // 记录当前能扩展到的最远距离
while (i < intervals.size && intervals[i].start <= currentEnd) {
// 更新能扩展到的最远距离
farthest = max(farthest, intervals[i].end)
i++
}
// 如果无法扩展,说明无法覆盖目标区间,返回-1
if (farthest == currentEnd) return -1
count++ // 选择一个区间
currentEnd = farthest // 更新已覆盖的最右边界
}
return count // 返回选择的区间数量
}
三、搜索算法(DFS/BFS)
算法解析
深度优先搜索(DFS)和广度优先搜索(BFS)是解决图和树相关问题的基础算法。DFS 通过递归不断深入探索分支,直到无法继续才回溯;BFS 则通过队列按层遍历,常用于寻找最短路径、最小步数等问题。在实际应用中,为了提高效率,常常需要使用剪枝(提前排除不可能的解)等优化手段。
真题实战
题目:迷宫问题(2018 年省赛)描述:给定一个迷宫地图,由0
(通路)和1
(墙壁)组成,寻找从起点到终点的最短路径。
data class Point(val x: Int, val y: Int)
fun mazeShortestPath(grid: Array<CharArray>, start: Point, end: Point): Int {
val rows = grid.size // 迷宫行数
val cols = grid[0].size // 迷宫列数
// 创建布尔数组记录每个位置是否已访问
val visited = Array(rows) { BooleanArray(cols) }
// 定义四个方向的偏移量
val directions = listOf(Point(-1, 0), Point(1, 0), Point(0, -1), Point(0, 1))
val queue = ArrayDeque<Point>() // 使用双端队列实现BFS
queue.add(start) // 将起点加入队列
visited[start.x][start.y] = true // 标记起点已访问
var steps = 0 // 记录步数
while (queue.isNotEmpty()) { // 队列不为空时继续搜索
val size = queue.size // 当前层的节点数量
for (i in 0 until size) { // 遍历当前层的所有节点
val current = queue.poll() // 取出当前节点
if (current == end) return steps // 找到终点,返回步数
for (dir in directions) { // 遍历四个方向
val nx = current.x + dir.x // 计算新位置的x坐标
val ny = current.y + dir.y // 计算新位置的y坐标
// 判断新位置是否在迷宫范围内,未访问且为通路
if (nx in 0 until rows && ny in 0 until cols &&!visited[nx][ny] && grid[nx][ny] == '0') {
visited[nx][ny] = true // 标记新位置已访问
queue.add(Point(nx, ny)) // 将新位置加入队列
}
}
}
steps++ // 层数增加,步数加1
}
return -1 // 无法到达终点,返回-1
}
四、图论算法(Graph Algorithms)
算法解析
图论算法在蓝桥杯中常涉及最短路径(Dijkstra、Floyd)、最小生成树(Kruskal)等问题。需要掌握图的存储结构,如邻接表和邻接矩阵,以及不同算法的适用场景和优化方法。例如,Dijkstra 算法适用于边权非负的单源最短路径问题,而 Floyd 算法可处理任意两点间的最短路径,但时间复杂度较高。
真题实战
题目:最短路径(2021 年国赛)描述:使用 Dijkstra 算法求图中给定起点到其他所有点的最短路径。
data class Edge(val to: Int, val weight: Int)
fun dijkstra(n: Int, edges: List<List<Edge>>, start: Int): IntArray {
// 创建数组dist记录起点到各点的最短距离,初始化为无穷大
val dist = IntArray(n) { Int.MAX_VALUE }
dist[start] = 0 // 起点到自身的距离为0
// 使用优先队列按距离排序,距离小的节点优先出队
val heap = PriorityQueue<Int> { a, b -> dist[a] - dist[b] }
heap.add(start) // 将起点加入优先队列
val visited = BooleanArray(n) // 记录节点是否已访问
while (heap.isNotEmpty()) { // 优先队列不为空时继续搜索
val u = heap.poll() // 取出距离最小的节点
if (visited[u]) continue // 如果已访问,跳过
visited[u] = true // 标记节点已访问
for (edge in edges[u]) { // 遍历节点u的所有出边
// 如果通过节点u到达edge.to的距离更短
if (dist[edge.to] > dist[u] + edge.weight) {
dist[edge.to] = dist[u] + edge.weight // 更新最短距离
heap.add(edge.to) // 将新节点加入优先队列
}
}
}
return dist // 返回起点到各点的最短距离数组
}
五、数学算法(Mathematical Algorithms)
算法解析
数学算法在蓝桥杯中主要涉及数论(如素数筛、快速幂、最大公约数)、组合数学(排列组合、二项式系数)等内容。由于蓝桥杯可能涉及大数运算,因此需要掌握高精度计算和模运算等技巧,以应对数据规模较大的情况。
真题实战
题目:素数筛(2020 年省赛)描述:求 1 到n
之间的素数个数。这里使用埃拉托斯特尼筛法,通过标记合数来筛选出素数。
fun sieveOfEratosthenes(n: Int): Int {
// 创建布尔数组isPrime,初始假设所有数都是素数
val isPrime = BooleanArray(n + 1) { true }
isPrime[0] = false // 0不是素数
isPrime[1] = false // 1不是素数
for (i in 2..n) { // 遍历从2到n的数
if (isPrime[i]) { // 如果i是素数
// 将i的倍数标记为合数
for (j in i * i..n step i) {
isPrime[j] = false
}
}
}
// 返回素数的个数
return isPrime.count { it }
}
六、字符串处理(String Manipulation)
算法解析
字符串处理问题在蓝桥杯中常考查字符串匹配(KMP 算法、正则表达式)、回文处理、子串查找等。解决这类问题时,需要注意字符串的边界条件,以及不同方法的时间复杂度,必要时可以通过预处理或哈希等手段优化效率。
真题实战
题目:最长回文子串(2017 年国赛)描述:在给定字符串中寻找最长的回文子串,即正向和反向读取都相同的子串。
fun longestPalindrome(s: String): String {
var start = 0 // 最长回文子串的起始位置
var end = 0 // 最长回文子串的结束位置
for (i in s.indices) { // 遍历字符串的每个字符
// 以当前字符为中心,扩展奇数长度的回文子串
val len1 = expandAroundCenter(s, i, i)
// 以当前字符和下一个字符为中心,扩展偶数长度的回文子串
val len2 = expandAroundCenter(s, i, i + 1)
val maxLen = max(len1, len2) // 取两种情况的较大长度
if (maxLen > end - start) { // 如果找到更长的回文子串
// 更新起始和结束位置
start = i - (maxLen - 1) / 2
end = i + maxLen / 2
}
}
// 返回最长回文子串
return s.substring(start, end + 1)
}
private fun expandAroundCenter(s: String, left: Int, right: Int): Int {
var l = left // 左指针
var r = right // 右指针
// 当左右指针在字符串内且字符相等时,继续扩展
while (l >= 0 && r < s.length && s[l] == s[r]) {
l--
r++
}
// 返回回文子串的长度
return r - l - 1
}
七、其他高频算法
位运算
位运算在蓝桥杯中常用于二进制位统计、状态压缩等问题。例如,通过位运算可以高效地判断一个数的二进制表示中 1 的个数,或者利用二进制位表示状态(如 2019 年 “糖果” 题)。
贪心 + 动态规划结合
有些问题需要将贪心算法和动态规划算法结合使用,例如 “砝码称重”(2021 年省赛),先通过贪心策略缩小问题规模,再用动态规划求解最优解。
分治算法
分治算法将问题分解为若干子问题,分别求解后合并结果,常用于归并排序、快速排序、逆序数计算等。例如在 “逆序数”(2018 年国赛)问题中,可以利用归并排序的过程统计逆序数。