蓝桥杯备赛:我用这5道贪心算法题,搞懂了区间问题的核心套路
蓝桥杯贪心算法实战5道区间问题教你掌握选择策略的精髓在算法竞赛的征途中贪心算法就像一位精明的商人总是在每个决策点做出当下最优的选择。这种短视的策略却往往能带来意想不到的整体收益。区间问题作为贪心算法的经典应用场景在蓝桥杯竞赛中频繁出现掌握其核心解题模式能让你在赛场上游刃有余。本文将带你深入剖析5道典型区间问题从基础到进阶逐步构建解决此类问题的通用思维框架。1. 贪心算法与区间问题的默契配合贪心算法之所以能在区间问题上大放异彩源于这类问题天然的局部最优可推导全局最优的特性。想象你是一位时间管理大师面对一堆需要安排的活动如何选择最多的互不冲突活动这就是典型的区间调度问题。贪心策略在这里展现出惊人的效率——只需按结束时间排序然后总是选择结束最早且不与已选区间冲突的活动。这种策略的正确性在于结束越早留给后续活动的时间就越多。就像收拾行李时先放最大件的物品这种最优化局部的思想往往能带来整体的最优解。贪心算法的核心要素最优子结构全局最优解包含子问题的最优解贪心选择性质局部最优选择能导致全局最优无后效性当前选择不影响后续子问题的结构对于区间问题我们通常需要先确定排序策略这是解题的关键第一步。常见的排序方式有按开始时间升序按结束时间升序按区间长度升序/降序# 区间按结束时间排序的Python实现 intervals [[1,3],[2,4],[1,2]] intervals.sort(keylambda x: x[1]) # 按第二个元素(结束时间)排序 print(intervals) # 输出: [[1, 2], [1, 3], [2, 4]]2. 基础题型无重叠区间问题问题描述给定一组区间找到需要移除区间的最小数量使剩余区间互不重叠。这是贪心算法最直接的体现也是理解区间问题的入门题型。解题思路将区间按结束时间升序排序初始化已选区间的结束边界为负无穷遍历排序后的区间如果当前区间的开始时间 ≥ 已选区间的结束时间则选择该区间并更新结束边界否则跳过该区间相当于移除算法正确性证明 假设存在一个最优解比我们的贪心解选择了更多的区间那么至少存在一个区间可以更早结束而被贪心算法选中这与贪心选择矛盾。因此贪心解就是最优解。import java.util.Arrays; import java.util.Comparator; public class NonOverlappingIntervals { public int eraseOverlapIntervals(int[][] intervals) { if (intervals.length 0) return 0; // 按结束时间升序排序 Arrays.sort(intervals, Comparator.comparingInt(a - a[1])); int count 1; // 至少可以选择第一个区间 int end intervals[0][1]; for (int i 1; i intervals.length; i) { if (intervals[i][0] end) { // 无重叠 count; end intervals[i][1]; } } return intervals.length - count; } }提示当区间边界相连不算重叠时判断条件为intervals[i][0] end若相连也算重叠则用intervals[i][0] end3. 进阶应用合并重叠区间问题变体给定一组区间合并所有重叠的区间。这与无重叠区间问题看似相反实则异曲同工都需要先排序后处理。解题步骤按开始时间升序排序区间初始化结果列表放入第一个区间遍历后续区间如果当前区间与结果列表中最后一个区间重叠则合并这两个区间取最大的结束时间否则将当前区间加入结果列表关键点合并策略取决于如何定义重叠。通常认为intervals[i][0] last[1]即为重叠。def merge(intervals): if not intervals: return [] # 按开始时间排序 intervals.sort(keylambda x: x[0]) merged [intervals[0]] for current in intervals[1:]: last merged[-1] if current[0] last[1]: # 有重叠 # 合并区间结束时间取两者较大值 merged[-1] [last[0], max(last[1], current[1])] else: merged.append(current) return merged # 示例 print(merge([[1,3],[2,6],[8,10],[15,18]])) # 输出: [[1,6],[8,10],[15,18]]复杂度分析时间复杂度O(nlogn)主要来自排序空间复杂度O(1)或O(n)取决于是否考虑输出空间4. 区间覆盖问题最少数量覆盖目标问题描述给定一个目标区间[s, e]和一组小区间选择最少数量的小区间完全覆盖目标区间且覆盖无重叠。贪心策略按开始时间升序排序小区间初始化当前覆盖的起点为s选择的区间列表为空在能覆盖当前起点的区间中选择结束时间最大的更新当前起点为该区间的结束时间重复直到覆盖整个目标区间关键点每次选择能覆盖当前起点且延伸最远的区间这是典型的贪心选择。import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; public class IntervalCover { public int[][] findCover(int[][] intervals, int[] target) { Arrays.sort(intervals, Comparator.comparingInt(a - a[0])); Listint[] result new ArrayList(); int currentStart target[0]; int i 0; while (currentStart target[1]) { int maxEnd currentStart; int chosen -1; // 找到能覆盖currentStart且结束最晚的区间 while (i intervals.length intervals[i][0] currentStart) { if (intervals[i][1] maxEnd) { maxEnd intervals[i][1]; chosen i; } i; } if (chosen -1) return new int[0][0]; // 无法覆盖 result.add(intervals[chosen]); currentStart maxEnd; } return result.toArray(new int[result.size()][]); } }实际应用这类问题在资源调度、课程安排等场景很常见。例如用最少的广告时段完全覆盖目标播放时间段。5. 复杂场景带权值的区间调度问题升级每个区间都有权值如收益选择一组互不重叠的区间使总权值最大。这需要动态规划与贪心思想的结合。解决方案按结束时间升序排序区间对每个区间i计算p[i] 最大的j使得区间j不与i重叠定义dp[i]前i个区间的最大权值和选择区间idp[p[i]] weight[i]不选区间idp[i-1]取两者较大值def maxWeightScheduling(intervals): intervals.sort(keylambda x: x[1]) # 按结束时间排序 n len(intervals) # 预处理p数组p[i]是不与i重叠的最大j p [-1] * n for i in range(1, n): left, right 0, i-1 while left right: mid (left right) // 2 if intervals[mid][1] intervals[i][0]: p[i] mid left mid 1 else: right mid - 1 # DP计算 dp [0] * n dp[0] intervals[0][2] # 假设权值是第三个元素 for i in range(1, n): include intervals[i][2] (dp[p[i]] if p[i] ! -1 else 0) exclude dp[i-1] dp[i] max(include, exclude) return dp[-1] # 示例每个区间格式为[start, end, weight] print(maxWeightScheduling([[1,2,5], [2,4,6], [1,3,2]])) # 输出: 8优化思路预处理p数组时使用二分查找将O(n²)优化到O(nlogn)6. 综合实战会议室安排问题问题描述给定一组会议时间区间问至少需要多少会议室才能安排所有会议。这是区间问题在实际中的典型应用。解题思路方法一将开始和结束时间分别排序使用双指针模拟会议室分配方法二使用最小堆跟踪正在使用的会议室的最早结束时间方法一实现public int minMeetingRooms(int[][] intervals) { int n intervals.length; int[] starts new int[n]; int[] ends new int[n]; for (int i 0; i n; i) { starts[i] intervals[i][0]; ends[i] intervals[i][1]; } Arrays.sort(starts); Arrays.sort(ends); int rooms 0; int endPtr 0; for (int i 0; i n; i) { if (starts[i] ends[endPtr]) { rooms; // 需要新会议室 } else { endPtr; // 复用会议室 } } return rooms; }方法二堆实现import heapq def minMeetingRooms(intervals): if not intervals: return 0 # 按开始时间排序 intervals.sort(keylambda x: x[0]) # 使用最小堆存储会议结束时间 heap [] heapq.heappush(heap, intervals[0][1]) for interval in intervals[1:]: # 如果当前会议开始时间 堆顶结束时间可以复用会议室 if interval[0] heap[0]: heapq.heappop(heap) heapq.heappush(heap, interval[1]) return len(heap)性能对比方法时间复杂度空间复杂度适用场景双指针O(nlogn)O(n)实现简单适合基础需求最小堆O(nlogn)O(n)更直观易于扩展7. 贪心算法的局限性与应对策略尽管贪心算法在区间问题上表现优异但它并非万能。当问题不满足贪心选择性质时贪心策略可能得到次优解。例如带权区间调度简单的贪心策略无法保证最优需要结合动态规划资源分配不均当区间对资源的需求量不同时贪心可能失效多维约束当区间涉及多个维度如时间空间时贪心难以直接应用应对策略混合方法结合贪心与其他算法如动态规划问题转化将复杂约束转化为贪心可处理的形式近似算法当贪心不能保证最优时可接受近似解# 动态规划与贪心结合的示例带权区间调度 def maxValueScheduling(intervals): intervals.sort(keylambda x: x[1]) # 按结束时间排序 dp [0] * len(intervals) dp[0] intervals[0][2] # 权值 for i in range(1, len(intervals)): # 找到不与当前区间重叠的前一个区间 j i - 1 while j 0 and intervals[j][1] intervals[i][0]: j - 1 include intervals[i][2] (dp[j] if j 0 else 0) exclude dp[i-1] dp[i] max(include, exclude) return dp[-1]在蓝桥杯竞赛中识别问题的贪心性质是关键。通常当问题具有选择-决策特性且局部最优能导向全局最优时贪心算法就是值得尝试的策略。通过大量练习区间类问题你会逐渐培养出对贪心策略的敏锐直觉在比赛中快速识别适用场景并高效解题。