Pattern 14 · Intervals

14 区间 先按起点排序,剩下的就是「重叠怎么办」

面试频率 高题目 5 道模板 2 个⭐认脸 0 道
§1 Mental Model 一句话心智模型

先记住这一句

区间题的第一件事永远是确定排序键:按起点,还是按终点。定下来之后只剩一个问题——当前区间和前一个重不重叠,以及重叠了怎么办。

按起点排序,用来合并、插入、判重叠(模板 A);按终点排序,用来「最多能留下几个互不重叠的」(模板 B 之外的那类贪心)。选错排序键,题就做不出来——这是本章唯一需要判断的地方。

「同时进行的最大数量」(会议室 II)是另一种形态:把起点和终点拆开各自排序,沿时间轴推进,开始 +1、结束 −1,峰值就是答案。这套写法叫扫描线,换成小顶堆存「最早结束时间」也是同一件事。

§2 Recognition Signals 识别信号

看到什么,想到本章

题面出现…想到
「合并重叠区间」按起点排序 + 合并(模板 A)
「插入一个新区间并合并」同上;不排序的三段式写法更快(57)
「最少删掉几个使其互不重叠」按终点排序贪心,留结束最早的
「需要几间会议室 / 最多同时有几个」扫描线或小顶堆(模板 B)
「用最少的箭射爆所有气球」「区间覆盖」按终点排序贪心,和 435 同源
输入是一堆 [start, end]先问自己:按起点排还是按终点排
分流条件 · 决策树同源

按终点排序那一类(435、452)本质上是贪心(第 15 章),只是贪心对象恰好是区间;把它们放在本章是因为识别信号来自「区间」这个形状。「区间和」是完全不同的题——那是前缀和(第 13 章),和排序无关。需要反复动态查询区间最值 / 区间修改的,属于线段树,本书明确不覆盖。

§3 Template 模板代码

可默写的那几行

■ 【骨架】 不变,闭着眼默写 ■ 【插槽】 随题变化,写题前先问自己填什么

模板 A · 排序 + 合并按起点排序
def merge_intervals(intervals):
    intervals.sort()                        # 【骨架】按起点排序 —— 区间题的第一行
    out = []
    for s, e in intervals:                  # 【骨架】
        if out and s <= out[-1][1]:         # 【插槽】重叠判定:<= 表示"碰到端点也算重叠"
            out[-1][1] = max(out[-1][1], e) # 【插槽】重叠了怎么办:合并 / 计数 / 丢一个
        else:
            out.append([s, e])              # 【骨架】不重叠就新开一段
    return out
默写口诀:排序、重叠就并、否则新开一段。s <= out[-1][1] 里的等号要不要,取决于题目认不认「端点相碰算重叠」——56 认,435 不认,见翻车点②。
模板 B · 扫描线求同时进行的最大数量
def min_meeting_rooms(intervals):
    starts = sorted(x[0] for x in intervals)      # 【骨架】起点和终点拆开各自排序
    ends = sorted(x[1] for x in intervals)
    rooms = best = j = 0
    for s in starts:                              # 【骨架】按时间推进,每来一个开始
        while j < len(ends) and ends[j] <= s:     # 【插槽】<= 表示"上一场刚结束就能接下一场"
            j += 1                                # 【骨架】先把已结束的房间腾出来
            rooms -= 1
        rooms += 1                                # 【骨架】占一间
        best = max(best, rooms)                   # 【插槽】答案:峰值 / 每一刻的值
    return best
起点和终点拆开排序之后,不需要知道哪个终点属于哪个起点——这是它比堆写法更省事的地方。
§4 Problems 题目分层

从建直觉到扭模板

标准模板直接套3 道
  • 模板 A 原题。本章刻意没有热身题:56 本身就是最小形态,任何比它更简单的区间题都不值得单独占一格。
  • 输入已按起点有序,所以不必再排:三段式扫过去——完全在新区间左边的直接放行、重叠的边合并边吞、剩下的直接接上。面试里能说出「已有序就别再排」是加分点。
  • 转成「最多能留下几个互不重叠的」,按终点排序贪心。留结束最早的,给后面留的空间最大——这句话就是它的贪心证明。
变体模板要扭,坑的来源2 道
  • 252 Meeting Rooms🔒 premiumEasy
    排序后看有没有相邻重叠,一行判定。它是 253 的前置台阶。
  • 253 Meeting Rooms II🔒 premiumMedium
    模板 B 原题。堆写法(堆里存最早结束时间,新会议来了先看能不能复用)也要能讲——面试官问「不拆开排序怎么做」时就是在等这个答案。
§5 Pitfalls & Self-check 常见翻车点 + 自测

会写不等于不翻车

  1. 排序键选错——合并类按起点,「最多保留几个」类按终点。435 若按起点排序,遇到 [[1,100],[2,3],[3,4]] 会留下那个超长区间,答案偏小。
  2. 端点相碰算不算重叠——56 认为 [1,4] 和 [4,5] 重叠(要合并),435 认为它们不重叠(都能留下)。同一个 <= / < 的差别,读题时必须先确定。
  3. 在遍历中修改排序后的列表——合并时习惯性 intervals.pop() 会打乱迭代。一律往新列表 out 里追加,只改 out[-1][1]。
  4. 扫描线的三步顺序——先释放已结束的、再占用新的、最后统计峰值。把统计提到释放之前,[[1,2],[2,3]] 这种首尾相接的输入会算成 2 间——房间数确实短暂到过 2,但那一刻上一场已经散了。对应模板里 while 在 rooms += 1 之前。
  5. 57 也去排序——输入已经有序还整体排一遍,复杂度从 O(n) 变回 O(n log n)。能过,但白丢了题目给的条件。

自测 · 30 秒识别模式

30 秒识别Q1 · 986 Interval List Intersections:给两个各自有序且内部不相交的区间列表,求它们的交集。你的第一反应是哪个模式?

看答案

区间 + 双指针。两个列表都已排序,交集是 [max(起点), min(终点)],非空就收下,然后终点小的那一方前进。「两个有序列表」这个形状同时也是第 02 章的信号,但处理对象是区间,模板在本章。

30 秒识别Q2 · 1288 Remove Covered Intervals:删除所有被其他区间覆盖的区间,返回剩余数量。你的第一反应是哪个模式?

看答案

区间。排序键是本题全部难点:起点升序、起点相同时终点降序,这样长的排在前面,后面凡是终点不超过当前最大终点的都被覆盖。

30 秒识别Q3 · 55 Jump Game:数组里每个数表示在该位置最多能向前跳几步,判断能否从起点跳到终点。你的第一反应是哪个模式?

看答案

贪心,不是本章——每个下标确实定义了一个「可达区间」,看起来很像区间题,但不需要排序也不需要合并:只要一路维护「目前能到的最远位置」,走到某处发现最远位置还不如当前下标就失败。见第 15 章。

每章自测都会混入恰好一道非本章题,防止「读哪章选哪章」的条件反射;题面也一律取自本章未收录的题,避免凭记忆而非凭信号作答。