👋 欢迎阅读

🎯 欢迎来到「无重叠区间」题解之旅! 本文将带你从"移除最少区间使剩余区间互不重叠"这一优化问题出发,深入理解贪心算法 的经典应用,并掌握如何通过按右端点排序 + 一次遍历高效地保留最多区间。
在开始之前,建议你先:
-
了解题目背景 :这是 LeetCode 435 题,给定若干区间
[start, end],要求移除最少数量的区间 ,使得剩余区间两两不重叠 (边界接触不算重叠)。本质上,这是一个区间调度 问题------我们希望保留尽可能多的互不重叠区间,移除的数量 = 总区间数 - 最多保留数。 -
明确学习目标 :掌握贪心选择策略 ------按右端点升序排序 ,然后依次选择结束最早 的区间作为第一个保留区间,之后每次选择起点 >= 上一个选中区间的终点 且终点最小 的区间。理解为什么这种"每次选结束最早"的策略能保留最多的区间(为后续留下更多空间),并熟练实现计数逻辑。
-
准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如
intervals = [[1,2],[2,3],[3,4],[1,3]]输出1)。
本文将从问题转化、贪心策略设计(按右端点排序 + 计数)、正确性直觉 到代码实现,层层递进。即使你对贪心算法还不熟悉,我们也会从"先结束的先安排,才能给后面的腾出更多时间 "这一直觉出发,让你轻松抓住核心思想 ------局部最优(每次选结束最早的)即全局最优(保留最多区间)。现在,让我们一起删掉最少的区间,让剩余区间和谐共处吧! 📅✂️
一、题目

二、做题思路
1. 问题分析(前置分析)
给定一组区间 [start, end],要求 移除最少数量的区间 ,使得剩余区间 互不重叠 (区间仅在端点接触不算重叠)。
核心转化 :最少删除数 = 总区间数 - 最多能保留的互不重叠区间数。
核心贪心思想 :按区间 左端点升序排序 ,依次扫描,当遇到重叠时,保留右端点更小的区间,因为右端点越小,给后续区间留出的空间越大,从而能保留更多区间。
2. 贪心策略(核心决策规则)
-
第一步 :将
intervals按 左端点升序排序。 -
第二步 :初始化 当前保留区间 为第一个区间,记录其右端点
right。 -
第三步:从第二个区间开始遍历:
-
若当前区间的 左端点 < 当前保留区间的右端点 (即重叠),则 必须删除其中一个区间。
-
为保留更多区间,删除右端点较大的区间 ,即 保留右端点更小的区间 :
right = min(right, currentRight)。 -
删除计数
ret++。
-
-
否则,说明 不重叠 ,更新当前保留区间为当前区间 :
right = currentRight。
-
-
第四步 :遍历结束后,
ret即为最少删除数。
3. 正确性说明(简单版本)
按左端点排序后,当两个区间重叠时,为了给后续区间腾出更多空间,必须删除右端点较大的那一个 ,因为右端点越大,与后续区间重叠的可能性就越高。每次遇到重叠都选择保留右端点较小的区间,这种 "局部最优" 策略能保证最终保留的区间数量最多,即删除的区间数量最少。这是 区间调度问题 的经典贪心解法,正确性由贪心选择性质保证:每次保留右端点最小的区间,不会影响后续的最优选择。
4. 实现细节(边界防护)
-
先对
intervals排序,时间复杂度 O(n log n)。 -
使用
right记录当前保留区间的右端点,初始为第一个区间的右端点。 -
遍历时,若
a < right(左端点小于当前保留区间的右端点),说明重叠:right = min(right, b),ret++。
-
若不重叠,则
right = b,更新当前保留区间。 -
若输入为空,直接返回 0(但题目保证
intervals.length >= 1)。
5. 返回值(目标映射)
返回 ret,即 最少需要移除的区间数量。
四、代码
cpp
class Solution
{
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals)
{
// 1. 贪心策略:按区间左端点升序排序,依次扫描,
// 当发现重叠时,删除右端点较大的区间(保留右端点较小的区间),
// 因为右端点越小,留给后续区间的空间越大,从而能保留更多区间。
// 本质是"区间调度"问题,求最少删除数 = 总区间数 - 最多不重叠区间数。
sort(intervals.begin(), intervals.end());
int row = intervals.size();
int col = intervals[0].size(); // 实际上每行都是2列,可省略
int ret = 0; // 记录需要删除的区间数量
int left = intervals[0][0]; // 当前保留区间的左端点(仅用于更新,实际未使用)
int right = intervals[0][1]; // 当前保留区间的右端点(用于比较重叠)
int a = 0, b = 0; // 当前遍历到的区间的左右端点
// 2. 从第二个区间开始遍历
for (int i = 1; i < row; i++)
{
a = intervals[i][0];
b = intervals[i][1];
// 如果当前区间与保留区间重叠(左端点 < 保留区间的右端点),
// 则需要删除其中一个,保留右端点较小的区间。
if (a < right)
{
// 更新右端点为两者较小值(删除右端点较大的区间,保留更小的右端点)
right = min(right, b);
ret++; // 删除一个区间(计数加1)
}
else
{
// 没有重叠,则更新当前保留区间为当前区间
left = a;
right = b;
}
}
// 3. 返回最少需要删除的区间数量
return ret;
}
};
五、流程图
六、正确性说明(详细版)
步骤 1:符号与问题建模
+----------------------------------------------------+
| 输入:区间列表 intervals,每个区间 [start, end] |
| 目标:删除最少数量的区间,使剩余区间互不重叠 |
| 注:[1,2] 和 [2,3] 视为不重叠 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 贪心策略:先按左端点升序排序,然后顺序扫描。 |
| 维护当前保留区间的右端点 right。 |
| 遍历下一区间 [a, b]: |
| - 若 a < right(重叠),则删除右端点较大的区间, |
| 即 right = min(right, b),删除计数 +1。 |
| - 否则(不重叠),更新 right = b,继续。 |
| 贪心实质:**重叠时,保留右端点较小的区间**, |
| 因为结束得早能为后续区间腾出更多空间, |
| 从而保留更多区间,删除更少。 |
+----------------------------------------------------+
-
按左端点升序排序,保证后续区间左端点 ≥ 当前区间的左端点。
-
维护当前保留区间的右端点
right,初始为第一个区间的右端点。 -
贪心选择 :每次遇到新区间时,若其左端点
< right,说明重叠,此时必须删除一个,选择保留右端点较小的那个(即删除右端点较大的),以保证未来空间最大。
步骤 2:关键性质 ------ 贪心选择的正确性(交换论证)
+----------------------------------------------------+
| 性质 1:若两个区间 [l1, r1] 和 [l2, r2] 重叠, |
| 且 l1 ≤ l2,则保留 r 较小的区间不会比保留较大的 |
| 区间更差。 |
| 证明:假设 r1 ≤ r2,若保留 r2 而删除 r1,则 |
| 之后任何与 r1 不重叠但可能与 r2 重叠的区间, |
| 在保留 r1 的方案中仍可被保留(因为 r1 更小), |
| 因此保留 r1 的方案**至少包含保留 r2 方案的所有 |
| 后续可选区间**,删除数不会更多。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 性质 2:当 a < right 时,当前区间与保留区间重叠, |
| 必须删除其中一个。保留右端点较小的区间是**唯一 |
| 不会损失后续最优性的选择**。 |
| 反证:若保留较大的右端点,则其覆盖范围更广, |
| 可能阻止更多后续区间被保留,导致删除数增加。 |
| 因此贪心选择必然是最优的。 |
+----------------------------------------------------+
详细论证:
-
性质1(重叠时保留更小右端点) :设当前保留区间为
[l1, r1],新区间为[l2, r2],且l1 ≤ l2(排序保证)。若l2 < r1,则二者重叠,必须删除一个。若保留 r1 而删除 r2 (即保留右端点较小的那个),则后续区间只要左端点 ≥ r1 即可不重叠;而若保留 r2(右端点较大),后续区间需要左端点 ≥ r2 才能不重叠,条件更严。因此保留 r1 不会比保留 r2 更差,因为它的结束时间更早,允许更多后续区间加入。 -
性质2(唯一最优选择) :当
a < right时,当前区间与保留区间重叠,必须至少删除一个。删除右端点较大的那个 (即保留较小的)是唯一能最大化后续可选空间的选择。若删除较小的而保留较大的,则后续可用空间变小,不可能得到更少的删除数。因此贪心选择是安全的。
步骤 3:归纳证明 ------ 扫描过程得到全局最优
+----------------------------------------------------+
| 初始:排序后,保留第一个区间,right = 第一个右端点。|
| 归纳假设:处理完前 i 个区间后,贪心已保留了 |
| 尽可能多的区间(即删除了最少数量的区间), |
| 且当前保留区间的右端点 right 是所有可行方案中 |
| 最小的(最优的)。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 处理第 i+1 个区间 [a, b]: |
| - 若 a < right,由性质1,**保留较小右端点**, |
| right = min(right, b),删除数 +1。 |
| - 否则,不重叠,**保留新区间**,right = b。 |
| 更新后,right 仍然是所有可行方案中最小的, |
| 归纳假设保持。 |
+----------------------------------------------------+
|
v
+----------------------------------------------------+
| 遍历结束后,贪心得到的保留区间数即为最大可能, |
| 删除数 = 总区间数 - 保留数,**达到最小值**。 |
+----------------------------------------------------+
详细论证:
-
归纳基础 :处理第一个区间,保留它,right 为其右端点。显然这是当前唯一方案,最优。
-
归纳步骤 :假设已处理前
i个区间,贪心算法得到的保留区间集合是所有可行方案中删除数最少 的,且right是所有最优方案中当前保留区间右端点最小 的(即最优的)。现在考虑第i+1个区间[a, b]:-
若
a < right,则与当前保留区间重叠。任何可行方案都必须至少删除当前区间或新区间中的一个 。由性质1,保留右端点较小的那个(即贪心选择)不会比保留较大的更差,且因为right已经是最小可行右端点,更新后right = min(right, b)仍然保持最小。删除数加1,仍是最优。 -
若
a >= right,则不重叠。贪心选择保留新区间 是安全的,因为新区间不会与已保留区间冲突,且保留它不会影响后续最优性(右端点变为b,可能比原先的right大,但这是为了覆盖新区间不得不做的更新;然而在给定已保留区间的前提下,保留新区间是唯一选择,因为若不保留它,就不可能再增加保留数)。此时right = b,仍然是在所有包含这些已保留区间的方案中右端点最小的选择。
-
-
终止 :遍历完所有区间后,贪心得到的保留区间数量不可能再增加,且由归纳假设每一步都保持最优,因此保留数最大,删除数最小。
🎯 闭幕
🎉 恭喜你完成了「无重叠区间」问题的学习!
为了巩固知识并进一步拓展,建议你:
🚀 动手实践
在 LeetCode 上提交代码,尝试不同的测试用例。
💡 深入思考
-
本题采用 排序 + 贪心 策略:按左端点升序排序后,扫描区间,当发现重叠时(
a < right),保留右端点更小的区间 ,删除右端点较大的区间,从而为后续区间留出更多空间。为什么保留右端点较小者能保证最终保留的区间数量最多? 如果保留右端点较大者,会有什么后果? -
贪心策略也可以按 右端点升序 排序来实现(更常见的区间调度写法),本代码按左端点排序与按右端点排序在最终结果上是否等价? 在时间复杂度上哪个更优?
📚 延伸挑战
- 将题目改为 求最多能保留多少个互不重叠的区间 ,你只需在代码中返回
row - ret即可,试试看。
如果你觉得本文对你有所帮助,欢迎:
👍 点赞 / 收藏
👤 关注作者,获取更多题解
💬 留言交流你的疑问或优化思路
祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨