LeetCode 435:无重叠区间(贪心算法) —— 题解

👋 欢迎阅读

🎯 欢迎来到「无重叠区间」题解之旅! 本文将带你从"移除最少区间使剩余区间互不重叠"这一优化问题出发,深入理解贪心算法 的经典应用,并掌握如何通过按右端点排序 + 一次遍历高效地保留最多区间。

在开始之前,建议你先:

  • 了解题目背景 :这是 LeetCode 435 题,给定若干区间 [start, end],要求移除最少数量的区间 ,使得剩余区间两两不重叠 (边界接触不算重叠)。本质上,这是一个区间调度 问题------我们希望保留尽可能多的互不重叠区间,移除的数量 = 总区间数 - 最多保留数。

  • 明确学习目标 :掌握贪心选择策略 ------按右端点升序排序 ,然后依次选择结束最早 的区间作为第一个保留区间,之后每次选择起点 >= 上一个选中区间的终点终点最小 的区间。理解为什么这种"每次选结束最早"的策略能保留最多的区间(为后续留下更多空间),并熟练实现计数逻辑。

  • 准备好环境 :建议在本地 IDE 或 LeetCode 在线编辑器中打开代码,边看边运行,亲手验证示例(如 intervals = [[1,2],[2,3],[3,4],[1,3]] 输出 1)。

本文将从问题转化、贪心策略设计(按右端点排序 + 计数)、正确性直觉 到代码实现,层层递进。即使你对贪心算法还不熟悉,我们也会从"先结束的先安排,才能给后面的腾出更多时间 "这一直觉出发,让你轻松抓住核心思想 ------局部最优(每次选结束最早的)即全局最优(保留最多区间)。现在,让我们一起删掉最少的区间,让剩余区间和谐共处吧! 📅✂️


一、题目

435. 无重叠区间 - 力扣(LeetCode)

二、做题思路

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 &lt; row; i++) 
    {
        a = intervals[i][0];
        b = intervals[i][1];

        // 如果当前区间与保留区间重叠(左端点 &lt; 保留区间的右端点),
        // 则需要删除其中一个,保留右端点较小的区间。
        if (a &lt; 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 即可,试试看。

如果你觉得本文对你有所帮助,欢迎:

👍 点赞 / 收藏

👤 关注作者,获取更多题解

💬 留言交流你的疑问或优化思路

祝你在 算法之路 上越走越稳,早日攻克每一道难题!下次见 🚀✨

相关推荐
技术小黑1 小时前
RNN算法实战系列05 | 天气预测
人工智能·rnn·算法
(Charon)3 小时前
【C++】锁与原子操作(四):自旋锁的完整实现与性能优化
c语言·开发语言·c++
Ivanqhz3 小时前
php8 use-def链
算法·php
evans在进步4 小时前
LeetCode 189 轮转数组:三次反转原地解决,图解 Java 实现
java·算法·leetcode
歌_顿4 小时前
Drawing_To_Model
算法
龍德明宇4 小时前
AI不会疼-龍德明宇
人工智能·算法·大语言模型llm·负主体性·ai存在论
ShineWinsu4 小时前
对于Linux:五种IO模型以及非阻塞IO的详细解析
linux·c++·面试·io·阻塞·非阻塞·fcntl
302wanger5 小时前
程序员写 SOP:把脑子里的流程固化下来
算法
科学实验家5 小时前
完全背包
算法
Smoothcloud润云5 小时前
GPU租赁数据安全怎么做?
人工智能·算法·ai·aigc·gpu算力·gpu