蓝桥杯必考算法解析及真题实战(Kotlin 实现)

一、动态规划(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 年国赛)问题中,可以利用归并排序的过程统计逆序数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值