

文章目录
-
- 摘要
- 描述
- 题解答案(Swift)
- 题解代码分析
- [1. 为什么按"结束时间"排序?](#1. 为什么按“结束时间”排序?)
- [2. 为什么用 `countNonOverlap = 1` 开始?](#2. 为什么用
countNonOverlap = 1开始?) - [3. 如何判断重叠?](#3. 如何判断重叠?)
- [4. 为什么结果是 `总区间数 - 不重叠的区间数`?](#4. 为什么结果是
总区间数 - 不重叠的区间数?) - [示例测试及结果(可运行 Demo)](#示例测试及结果(可运行 Demo))
- 与实际场景结合
-
- [1. 会议室预定冲突检测](#1. 会议室预定冲突检测)
- [2. 排班系统](#2. 排班系统)
- [3. 任务调度 / CPU 调度](#3. 任务调度 / CPU 调度)
- [4. 数据清洗(归并日志 / 去重)](#4. 数据清洗(归并日志 / 去重))
- 时间复杂度
- 空间复杂度
- 总结
摘要
这篇文章我们来聊一个在实际工作中经常遇到的经典问题 ------ "如何从一堆区间里删掉尽可能少的,让剩下的区间彼此之间没有重叠?"。
这道题在排班、会议室调度、任务分段处理、日程去重、数据清洗等真实业务场景中几乎都会遇到。算法本身不复杂,但要真正理解为什么这样做,需要一点点耐心。
我们会用 Swift 写出完整的解法代码,做可运行的 Demo,然后拆开讲每一步为什么这么做,让你在业务中也能轻松复用。

描述
题目给了我们一组区间:
txt
intervals[i] = [start, end]
目标很简单:我们要移除尽可能少的区间,让剩下的所有区间互不重叠。
题目特别强调:
[1,2]和[2,3]不算重叠。- 只有当"结束时间 > 下一个开始时间"才算真的 overlap。
示例:
txt
[1,2] [2,3] [3,4] [1,3]
这里只有 [1,3] 和别人有重叠,只要移除这一段就够了,所以答案是 1。

题解答案(Swift)
解决这类"区间调度"问题,最稳、最常见、最贪心的办法就是:
按照结束时间升序排序,然后每次优先选择最先结束的区间。
因为:
- 一个区间结束得越早,就越不容易挡住后面的区间;
- 保留更多不冲突的区间,自然就意味着删除更少。
完整代码如下:
swift
class Solution {
func eraseOverlapIntervals(_ intervals: [[Int]]) -> Int {
if intervals.count <= 1 { return 0 }
// 1. 按结束时间排序
let sorted = intervals.sorted { $0[1] < $1[1] }
var countNonOverlap = 1 // 至少能选一个
var prevEnd = sorted[0][1]
// 2. 贪心选择之后所有不重叠的区间
for i in 1..<sorted.count {
let interval = sorted[i]
if interval[0] >= prevEnd {
// 不重叠:选择它
countNonOverlap += 1
prevEnd = interval[1]
}
}
// 3. 总区间数 - 不重叠区间数 = 需要移除的数量
return intervals.count - countNonOverlap
}
}
题解代码分析
下面我们把这段代码的核心逻辑拆开讲一下。
1. 为什么按"结束时间"排序?
比如有两段区间:
txt
[1,3] 和 [1,2]
如果你优先选 [1,3],你就只能要一个。
但如果你选 [1,2],你后面还有机会选择更多不冲突的区间。
这就是贪心策略的核心:
尽量让每次选择留下更多空间给后面的区间。
2. 为什么用 countNonOverlap = 1 开始?
因为按照结束时间排好序后,最早结束的肯定是最佳选择,所以我们先选第一个。
3. 如何判断重叠?
非常简单:
swift
if interval[0] >= prevEnd
如果下一个区间的开始时间 ≥ 上一个区间的结束时间,就不算重叠。
4. 为什么结果是 总区间数 - 不重叠的区间数?
我们挑选尽量多的不冲突区间(maximize non-overlap),
而题目要求删除尽量少的(min remove)。
这两个是完全等价的反向操作。
示例测试及结果(可运行 Demo)
下面提供一段可运行的 Swift Playground Demo。
swift
import Foundation
class Solution {
func eraseOverlapIntervals(_ intervals: [[Int]]) -> Int {
if intervals.count <= 1 { return 0 }
let sorted = intervals.sorted { $0[1] < $1[1] }
var countNonOverlap = 1
var prevEnd = sorted[0][1]
for i in 1..<sorted.count {
let interval = sorted[i]
if interval[0] >= prevEnd {
countNonOverlap += 1
prevEnd = interval[1]
}
}
return intervals.count - countNonOverlap
}
}
// Demo
let solution = Solution()
let test1 = [[1,2],[2,3],[3,4],[1,3]]
print("输入:", test1)
print("需要移除数量:", solution.eraseOverlapIntervals(test1), "\n")
let test2 = [[1,2],[1,2],[1,2]]
print("输入:", test2)
print("需要移除数量:", solution.eraseOverlapIntervals(test2), "\n")
let test3 = [[1,2],[2,3]]
print("输入:", test3)
print("需要移除数量:", solution.eraseOverlapIntervals(test3), "\n")
let test4 = [[1,5],[2,3],[3,4]]
print("输入:", test4)
print("需要移除数量:", solution.eraseOverlapIntervals(test4), "\n")
输出结果:
txt
输入: [[1, 2], [2, 3], [3, 4], [1, 3]]
需要移除数量: 1
输入: [[1, 2], [1, 2], [1, 2]]
需要移除数量: 2
输入: [[1, 2], [2, 3]]
需要移除数量: 0
输入: [[1, 5], [2, 3], [3, 4]]
需要移除数量: 1
与实际场景结合
这个算法在真实业务里用得非常多,举几个常见场景:
1. 会议室预定冲突检测
如果你有一堆会议记录,需要清理成不冲突的会议安排,这个算法就是最优方案。
2. 排班系统
多个员工之间排班冲突,往往需要找出最少需要调整的区间。
3. 任务调度 / CPU 调度
操作系统里常见的调度算法,比如 Interval Scheduling Optimization,原理完全一样。
4. 数据清洗(归并日志 / 去重)
日志里经常有时间段交错的区间,清理成无重叠区间就是这类算法。
你会发现,几乎所有"时间段管理"类场景都能用这道题的思路解决。
时间复杂度
核心是排序:
- 排序:O(n log n)
- 遍历:O(n)
总复杂度:
O(n log n)
空间复杂度
我们只使用了常量空间(除了排序本身需要的空间):
O(1) 额外空间
(如果把排序的空间开销算进去则为 O(n),算法本体仍然是 O(1))
总结
LeetCode 435 是区间贪心问题中最典型的题之一。解法其实就一句话:
按结束时间排序 + 贪心选不重叠区间
这类题的核心不是写代码,而是理解为什么 "结束更早的区间更优"。一旦理解这个策略,所有类似的区间题基本都能秒掉。