引言
贪心算法(Greedy Algorithm)是一种在每一步选择中都采取当前最优解的算法策略。它的核心思想是在求解过程中,始终做出当前看来最好的选择,希望通过局部最优选择达到全局最优的结果。
区间调度问题是学习贪心算法的经典入门题型,它能够很好地展示贪心算法的精髓和应用场景。
问题描述
问题定义:给定若干个区间,选择尽可能多的区间,使得这些区间互不重叠。
例如:
- 区间集合:[[1,3], [2,6], [8,10], [15,18]]
- 最优解:选择 [1,3], [8,10], [15,18],共3个区间
贪心策略分析
对于区间调度问题,我们可能会考虑以下几种贪心策略:
策略1:选择起始时间最早的区间
反例:[[1,100], [2,3], [4,5]]
- 贪心选择:[1,100]
- 最优解:[2,3], [4,5]
- 结论:此策略不可行
策略2:选择持续时间最短的区间
反例:[[1,2], [2,100], [3,4]]
- 贪心选择:[1,2], [3,4]
- 但如果有 [[1,2], [1.5,2.5], [2,3]],最短区间策略可能不是最优
策略3:选择结束时间最早的区间 ✓
这是正确的贪心策略!
证明思路:
- 设最优解为 OPT,贪心解为 GREEDY
- 假设贪心算法选择的第一个区间 g₁ 不在 OPT 中
- 设 OPT 中第一个区间为 o₁
- 由于 g₁.end ≤ o₁.end,我们可以用 g₁ 替换 o₁
- 递归证明后续选择的正确性
算法实现
Java实现
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
| import java.util.*;
public class IntervalScheduling { public static class Interval { int start, end; public Interval(int start, int end) { this.start = start; this.end = end; } @Override public String toString() { return "[" + start + "," + end + "]"; } }
public static int eraseOverlapIntervals(int[][] intervals) { if (intervals.length <= 1) return 0; Arrays.sort(intervals, (a, b) -> a[1] - b[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; }
public static List<Interval> findMaxIntervals(int[][] intervals) { if (intervals.length == 0) return new ArrayList<>(); List<Interval> list = new ArrayList<>(); for (int[] interval : intervals) { list.add(new Interval(interval[0], interval[1])); } list.sort((a, b) -> a.end - b.end); List<Interval> result = new ArrayList<>(); result.add(list.get(0)); for (int i = 1; i < list.size(); i++) { Interval current = list.get(i); Interval last = result.get(result.size() - 1); if (current.start >= last.end) { result.add(current); } } return result; } }
|
Python实现
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
| def erase_overlap_intervals(intervals): """ 区间调度问题 - 贪心算法 :param intervals: List[List[int]] 区间列表 :return: int 需要删除的最少区间数 """ if len(intervals) <= 1: return 0 intervals.sort(key=lambda x: x[1]) count = 1 end = intervals[0][1] for i in range(1, len(intervals)): if intervals[i][0] >= end: count += 1 end = intervals[i][1] return len(intervals) - count
def find_max_intervals(intervals): """ 返回具体的最优调度方案 """ if not intervals: return [] intervals.sort(key=lambda x: x[1]) result = [intervals[0]] for i in range(1, len(intervals)): if intervals[i][0] >= result[-1][1]: result.append(intervals[i]) return result
if __name__ == "__main__": intervals1 = [[1,2], [2,3], [3,4], [1,3]] print(f"输入: {intervals1}") print(f"需要删除的区间数: {erase_overlap_intervals(intervals1)}") print(f"最优方案: {find_max_intervals(intervals1)}") print() intervals2 = [[1,2], [1,2], [1,2]] print(f"输入: {intervals2}") print(f"需要删除的区间数: {erase_overlap_intervals(intervals2)}") print(f"最优方案: {find_max_intervals(intervals2)}")
|
复杂度分析
时间复杂度:O(n log n)
空间复杂度:O(1)
相关LeetCode题目
435. 无重叠区间
452. 用最少数量的箭引爆气球
253. 会议室 II
算法应用
区间调度贪心算法在现实中有广泛应用:
会议室预订系统
CPU任务调度
资源分配问题
总结
区间调度问题完美展示了贪心算法的核心思想:
- 贪心选择性质:问题的整体最优解可以通过一系列局部最优选择得到
- 最优子结构:问题的最优解包含子问题的最优解
- 关键策略:选择结束时间最早的区间,为后续留出最多空间
通过这个经典问题,我们学会了:
- 如何分析和验证贪心策略的正确性
- 贪心算法的证明方法
- 实际应用中的算法优化
在下一篇文章中,我们将探讨另一个贪心算法的经典应用:分发糖果问题。
💡 思考题: 如果题目变成”选择总时长最长的不重叠区间组合”,贪心策略还适用吗?为什么?
📚 扩展阅读:
- 《算法导论》第16章 贪心算法
- 《算法设计与分析基础》第9章