LeetCode 56. 合并区间:区间重叠问题的核心解法与代码解析

在LeetCode区间类题目中,56. 合并区间是经典的中等题,也是后续解决复杂区间问题(如插入区间、无重叠区间)的基础。它看似简单,却考察了对区间排序、边界判断的核心能力,很多新手会在"如何判断重叠""如何更新区间边界"上踩坑。今天这篇博客,我们就从题目解读入手,一步步推导解题思路,逐行解析给出的代码,再补充易错点和优化方向,帮大家彻底吃透这道题,掌握区间重叠问题的解题模板。

一、题目解读:明确"合并重叠区间"的核心要求

先吃透题干,避免因理解偏差走弯路,题干核心信息拆解如下:

  • 输入:一个二维数组intervals,其中每个元素intervals[i] = [starti, endi] 表示一个区间(starti是区间起始值,endi是区间结束值),输入区间无固定排序规则;

  • 输出:一个不重叠的区间数组,要求满足两个核心条件------① 覆盖输入中的所有区间(无遗漏);② 区间之间互不重叠(无冗余);

  • 关键提示:重叠的定义的是"区间之间有交集或相邻",比如[1,3]和[2,6]重叠(交集为[2,3]),[1,4]和[4,5]也需要合并(相邻,合并为[1,5])。

举两个典型示例,帮大家直观理解需求:

示例1:输入intervals = [[1,3],[2,6],[8,10],[15,18]],输出[[1,6],[8,10],[15,18]]。原因:[1,3]和[2,6]重叠,合并为[1,6],其余区间无重叠,直接保留。

示例2:输入intervals = [[1,4],[4,5]],输出[[1,5]]。原因:两个区间相邻,属于可合并范围,合并后覆盖所有输入区间且无重叠。

这里有个容易忽略的点:输入区间是无序的------这是解题的第一个关键点。如果区间无序,我们无法直接判断相邻区间是否重叠,因此"排序"是解题的前置步骤,也是核心突破口之一。

二、解题思路:排序 + 遍历合并,构建无重叠区间

结合题干特点,我们可以提炼出"排序先行、遍历合并"的核心思路,步骤清晰且可复用,具体拆解如下:

  1. 排序预处理:将二维数组intervals按照"每个区间的起始值starti"升序排序。排序后,所有可能重叠的区间会相邻,这是我们能够线性遍历合并的前提;

  2. 初始化结果集:用一个空数组result存储最终的无重叠区间,后续我们会逐步将合并后的区间加入其中;

  3. 线性遍历合并:遍历排序后的每个区间interval,分两种情况处理:

    • 如果result为空(即还没有处理任何区间),直接将当前区间interval加入result,作为第一个待合并的区间;

    • 如果result不为空,取出result中最后一个区间(记为lastInterval,这是当前最右侧的待合并区间),对比当前区间interval的起始值与lastInterval的结束值:

      • 若interval[0] ≤ lastInterval[1]:说明两个区间重叠(或相邻),需要合并,合并后的区间起始值不变(仍为lastInterval[0]),结束值取两个区间结束值的最大值(避免出现"一个区间包含另一个区间"的情况,如[1,5]和[2,4],合并后应为[1,5]);

      • 若interval[0] > lastInterval[1]:说明两个区间无重叠,直接将当前区间interval加入result,作为新的待合并区间。

  4. 返回结果:遍历结束后,result中存储的就是所有合并后的无重叠区间,直接返回即可。

补充对比:这道题和之前提到的"汇总区间"(LeetCode 228)有本质区别------汇总区间是"从有序整数数组提炼区间",输入是单个整数的有序集合;而合并区间是"从区间数组合并重叠区间",输入是多个区间的集合,核心难点在于"排序"和"重叠判断",而非"区间提炼"。

三、代码逐行解析:吃透每一步的逻辑的意义

给出的代码是TypeScript版本,逻辑简洁、高效,完全贴合上述解题思路,我们逐行拆解,搞懂每一步的作用,避免"背代码"却不懂原理:

typescript 复制代码
function merge(intervals: number[][]): number[][] {
  // 1. 初始化结果集,用于存储合并后的无重叠区间
  const result: number[][] = [];
  // 2. 按照区间起始值升序排序,关键预处理步骤
  intervals.sort((a, b) => a[0] - b[0]);
  // 3. 遍历排序后的所有区间,逐个合并
  for (const interval of intervals) {
    // 3.1 若结果集为空,直接加入当前区间(第一个区间无需合并)
    if(result.length === 0){
      result.push(interval);
    } else {
      // 3.2 取出结果集最后一个区间(当前最右侧待合并区间)
      const lastInterval = result[result.length - 1];
      // 3.3 判断当前区间与最后一个区间是否重叠(或相邻)
      if (interval[0] <= lastInterval[1]) {
        // 重叠:更新最后一个区间的结束值为两者最大值
        lastInterval[1] = Math.max(lastInterval[1], interval[1]);
      } else {
        // 不重叠:直接加入结果集
        result.push(interval);
      }
    }
  }
  // 4. 返回合并后的无重叠区间数组
  return result;
};

关键代码细节解读(易错点重点标注)

  • 排序逻辑:intervals.sort((a, b) => a[0] - b[0]) ------ 这里必须按照"区间起始值a[0]"排序,不能按结束值排序。如果按结束值排序,会导致相邻区间可能并非潜在重叠区间,无法正确合并(如[[1,5],[2,3]],按结束值排序后为[[2,3],[1,5]],遍历会误判为不重叠);

  • 重叠判断条件:interval[0] ≤ lastInterval[1] ------ 注意是"≤",不是"<"。因为当两个区间相邻时(如[1,4]和[4,5]),interval[0] = lastInterval[1],此时也需要合并,否则会出现两个相邻的区间,不符合"无重叠且全覆盖"的要求;

  • 区间更新逻辑:lastInterval[1] = Math.max(lastInterval[1], interval[1]) ------ 这一步是为了处理"一个区间包含另一个区间"的情况。比如输入[[1,5],[2,3]],排序后遍历到[2,3]时,interval[0](2)≤ lastInterval[1](5),此时合并后的结束值应为5(两者最大值),而非3,避免丢失原区间的范围。

四、复杂度分析与优化方向

1. 时间复杂度

核心耗时在于"排序"和"遍历":排序的时间复杂度为O(n log n)(n为区间的个数),遍历的时间复杂度为O(n),因此整体时间复杂度为O(n log n),这是该题的最优时间复杂度(因为必须通过排序才能让潜在重叠区间相邻,排序的最低时间复杂度就是O(n log n))。

2. 空间复杂度

空间复杂度分两种情况:

  • 若不考虑排序所需的额外空间(不同语言的sort实现占用空间不同,如JavaScript的sort是原地排序,占用O(log n)空间),仅考虑结果集result,空间复杂度为O(n)(最坏情况:所有区间都不重叠,result需要存储所有区间);

  • 若考虑排序的额外空间,整体空间复杂度为O(n log n)。

3. 优化方向

给出的代码已经是最优解法,无需大幅修改,但有两个小细节可以优化(不影响复杂度,仅提升代码简洁度):

  • 可以将"result为空"的判断与后续逻辑合并,简化代码(如下所示);

  • 对于空输入(intervals = []),代码会直接返回空数组,无需额外处理(因为sort方法对空数组无影响,遍历也不会执行)。

typescript 复制代码
// 简化版代码(逻辑不变,更简洁)
function merge(intervals: number[][]): number[][] {
  const result: number[][] = [];
  intervals.sort((a, b) => a[0] - b[0]);
  for (const interval of intervals) {
    // 合并判断逻辑,无需单独写if判断result是否为空
    if (!result.length || interval[0] > result[result.length - 1][1]) {
      result.push(interval);
    } else {
      result[result.length - 1][1] = Math.max(result[result.length - 1][1], interval[1]);
    }
  }
  return result;
};

五、常见易错点总结(避坑必看)

新手做这道题,大概率会在以下3个地方出错,提前规避:

  1. 忘记排序,直接遍历合并:会导致相邻区间并非潜在重叠区间,无法正确合并(如输入[[1,3],[8,10],[2,6]],不排序会误判[1,3]和[8,10]不重叠,再遍历[2,6]时,也无法和[1,3]合并,最终结果错误);

  2. 重叠判断条件写错,用"<"代替"≤":会导致相邻区间无法合并(如[[1,4],[4,5]],会被误判为不重叠,输出[[1,4],[4,5]],不符合题目要求);

  3. 合并时直接覆盖结束值,不用Math.max:会导致"大区间包含小区间"时,丢失大区间的范围(如[[1,5],[2,3]],会误合并为[1,3],丢失原区间[1,5]的范围)。

六、题目延伸:举一反三,掌握区间类题目套路

合并区间是区间类题目的"基础模板",掌握它之后,再做类似题目会事半功倍,推荐3道延伸题目,帮大家巩固思路:

  • LeetCode 57. 插入区间:在合并区间的基础上,新增一个区间,插入后再合并,核心思路仍是"排序+合并",只是多了"插入"的步骤;

  • LeetCode 435. 无重叠区间:求需要移除的最少区间数,使剩余区间无重叠,核心仍是"排序+重叠判断",只是逻辑改为"统计重叠区间个数";

  • LeetCode 228. 汇总区间:与合并区间对比,输入是有序整数数组,核心是"提炼区间",而非"合并区间",可以对比练习,避免混淆。

七、总结

LeetCode 56. 合并区间的核心逻辑其实很简单:排序让重叠区间相邻,遍历过程中逐个判断、合并。这道题的重点不在于代码有多复杂,而在于对"排序的必要性""重叠条件的判断""区间边界的更新"这三个细节的把控。

记住一句话:区间类题目,只要涉及"重叠""排序",优先考虑"按起始值排序 + 线性遍历合并"的模板,再根据具体题目调整细节(如插入、统计重叠个数等)。

相关推荐
Lionel6894 小时前
分步实现 Flutter 鸿蒙轮播图核心功能(搜索框 + 指示灯)
算法·图搜索算法
张3蜂5 小时前
深入理解 Python 的 frozenset:为什么要有“不可变集合”?
前端·python·spring
无小道5 小时前
Qt——事件简单介绍
开发语言·前端·qt
小妖6665 小时前
js 实现快速排序算法
数据结构·算法·排序算法
广州华水科技5 小时前
GNSS与单北斗变形监测技术的应用现状分析与未来发展方向
前端
xsyaaaan5 小时前
代码随想录Day30动态规划:背包问题二维_背包问题一维_416分割等和子集
算法·动态规划
code_YuJun5 小时前
corepack 作用
前端
千寻girling5 小时前
Koa.js 教程 | 一份不可多得的 Node.js 的 Web 框架 Koa.js 教程
前端·后端·面试
全栈前端老曹5 小时前
【MongoDB】Node.js 集成 —— Mongoose ORM、Schema 设计、Model 操作
前端·javascript·数据库·mongodb·node.js·nosql·全栈