区间类题目是LeetCode中数组模块的高频考点,其中「插入区间」更是结合了“排序”与“合并”的核心思路,既考察对边界条件的把控,也考验代码的简洁性和效率。今天我们就以LeetCode 57题为例,从基础实现入手,逐步优化代码,吃透这道经典面试题。
一、题目解析(清晰理解需求)
题目给出两个关键条件,这是解题的前提,必须先明确:
-
给定的区间列表
intervals是无重叠的,且按照「区间起始端点starti」升序排列; -
需要插入一个新区间
newInterval,插入后要求:列表依然升序、区间无重叠(必要时合并区间); -
注意:不需要原地修改原数组,可创建新数组返回。
举两个典型示例,帮大家快速get题意:
示例1:intervals = [[1,3],[6,9]], newInterval = [2,5] → 输出 [[1,5],[6,9]]
解析:新区间[2,5]与[1,3]重叠,合并为[1,5],与[6,9]无重叠,直接拼接。
示例2:intervals = [[1,2],[3,5],[6,7],[8,10],[12,16]], newInterval = [4,8] → 输出 [[1,2],[3,10],[12,16]]
解析:新区间[4,8]与[3,5]、[6,7]、[8,10]均重叠,合并为[3,10]。
核心难点:如何高效判断“新区间与原有区间是否重叠”,以及“如何合并重叠区间”,同时避免冗余操作、提升代码效率。
二、基础实现(insert_1):先跑通,再优化
先从最直观的思路入手,写出能正确解题的代码,再分析其中的可优化点。基础思路分为3步:处理无重叠左区间、处理重叠区间、处理无重叠右区间。
2.1 基础解法代码
function insert_1(intervals: number[][], newInterval: number[]): number[][] {
if(!intervals.length){
return [newInterval];
}
const res: number[][] = [];
let i = 0;
// 1. 先把所有在newInterval左边、完全不重叠的区间放入结果(区间右端 < 新区间左端)
while (intervals[i] && intervals[i][1] < newInterval[0]) {
res.push(intervals[i]);
i++;
}
// 边界处理:如果所有区间都在新区间左边,直接加入新区间返回
if(!intervals[i]){
res.push(newInterval);
return res;
}
// 2. 处理与newInterval相干(重叠/相邻)的区间
if (intervals[i][0] > newInterval[1]) {
// 情况1:当前区间在新区间右边,无重叠,先加新区间,再加当前区间
res.push(newInterval);
res.push(intervals[i]);
} else {
// 情况2:有重叠,合并当前区间与新区间,加入结果
res.push([Math.min(intervals[i][0], newInterval[0]), Math.max(intervals[i][1], newInterval[1])]);
}
i++;
// 3. 处理剩余区间,判断是否与结果中最后一个区间(已合并的区间)重叠
while (i < intervals.length) {
const end = res[res.length - 1];
if (intervals[i][0] > end[1]){
// 无重叠,直接加入
res.push(intervals[i]);
}else{
// 有重叠,合并,更新最后一个区间的右端
end[1] = Math.max(end[1], intervals[i][1]);
res.pop(); // 冗余操作:弹出再推入,其实可直接修改
res.push(end);
}
i++;
}
return res;
};
2.2 基础解法思路总结
这段代码的逻辑是“分步处理、逐个判断”,核心是先分离无重叠的左区间,再处理重叠区间,最后处理无重叠的右区间。优点是逻辑清晰、边界考虑全面,能覆盖所有测试用例(比如空区间、新区间在最左/最右、新区间覆盖所有区间等)。
但缺点也很明显:存在冗余操作,导致时间和空间效率有优化空间,我们重点看两个可优化点:
-
空间冗余:合并右区间时,执行
res.pop() + res.push(end),其实可以直接修改结果数组中最后一个元素,无需弹出再推入(数组的pop/push操作会涉及底层内存重分配,增加开销); -
逻辑冗余:重叠区间的处理拆分为“单次判断”,需要额外处理“当前区间是否存在”的边界,可简化为“循环合并所有重叠区间”;
-
判断冗余:
intervals[i]的存在性判断可通过数组长度简化,无需重复判断。
三、优化实现(insert_2):精简逻辑,提升效率
针对基础解法的冗余问题,我们优化核心思路:合并重叠区间时,直接更新新区间的边界,而非频繁操作结果数组,同时精简边界判断,让代码更紧凑、效率更高。
3.1 优化解法代码
function insert_2(intervals: number[][], newInterval: number[]): number[][] {
const res: number[][] = [];
let i = 0;
const n = intervals.length;
// 1. 先把所有在newInterval左边且不重叠的区间加入结果(和基础版一致,但精简了判断)
while (i < n && intervals[i][1] < newInterval[0]) {
res.push(intervals[i]);
i++;
}
// 2. 合并所有与newInterval重叠的区间(核心优化点)
while (i < n && intervals[i][0] <= newInterval[1]) {
// 直接更新newInterval的边界,无需操作res数组
newInterval[0] = Math.min(newInterval[0], intervals[i][0]);
newInterval[1] = Math.max(newInterval[1], intervals[i][1]);
i++;
}
// 3. 将合并后的新区间加入结果(仅1次push操作)
res.push(newInterval);
// 4. 把剩余在newInterval右边且不重叠的区间加入结果
while (i < n) {
res.push(intervals[i]);
i++;
}
return res;
}
3.2 核心优化点拆解(重点看这里!)
优化后的代码,核心是“减少数组操作、精简逻辑判断”,具体优化点如下,结合时间和空间效率分析:
1. 空间效率优化(关键!)
基础版中,合并重叠区间时,会频繁对结果数组执行 pop() + push() 操作——这是完全不必要的。因为结果数组的最后一个元素,本质就是“当前已合并的区间”,我们可以直接修改它的边界,无需弹出再推入。
优化版的处理方式:不操作结果数组,而是直接更新 newInterval 的左右边界,将所有重叠的区间都合并到 newInterval 中,最后只执行1次 res.push(newInterval)。这样就减少了数组增删操作的开销,空间使用更高效(虽然整体空间复杂度还是O(n),但中间过程的临时开销大幅降低)。
2. 时间效率优化
时间复杂度依然是O(n)(n为区间数量),因为我们只遍历了一次原数组,每个区间只被处理一次。但优化后:
-
减少了循环内的条件判断次数(比如移除了
if(!intervals[i])的冗余判断,用i < n天然处理边界); -
移除了
pop() + push()的冗余操作(数组增删的时间开销高于直接修改元素); -
合并逻辑更紧凑,减少了分支跳转的时间开销。
3. 逻辑精简,可读性提升
优化后的代码,将整个流程拆分为4个清晰、连贯的步骤,无需额外的边界判断(比如“所有区间在新区间左边”的情况,会被第1个循环和第3步自然覆盖),代码行数减少,逻辑更易理解,也更符合“高效算法”的编码习惯。
四、两种解法对比(一目了然)
| 对比维度 | 基础解法(insert_1) | 优化解法(insert_2) |
|---|---|---|
| 时间复杂度 | O(n)(但循环内操作冗余) | O(n)(循环内操作精简,实际效率更高) |
| 空间复杂度 | O(n)(冗余的pop/push操作增加内存开销) | O(n)(无冗余操作,空间使用更高效) |
| 逻辑复杂度 | 较高,多分支判断、冗余边界处理 | 较低,流程清晰,无冗余判断 |
| 适用场景 | 新手入门,便于理解分步思路 | 面试/实际开发,追求高效简洁 |
五、易错点提醒(避坑必备)
做这道题时,很多人会栽在边界条件上,结合两种解法,总结3个高频易错点:
-
空区间处理:当
intervals为空时,直接返回[newInterval](基础版和优化版都考虑到了,但容易遗漏); -
重叠边界判断:重叠的条件是「当前区间左端 ≤ 新区间右端」,而非「当前区间左端 < 新区间右端」——比如区间[1,5]和[5,7]是相邻的,也需要合并;
-
新区间在最右/最左:新区间完全在所有区间右边时,遍历完所有左区间后,直接加入新区间即可;完全在左边时,先加入新区间,再加入所有原区间。
六、总结与延伸
LeetCode 57题的核心是「区间合并」,解题的关键思路是:利用原数组的有序性,分步骤处理“无重叠左区间、重叠区间、无重叠右区间”,优化的核心是“减少数组操作、精简逻辑判断”。
从基础版到优化版,我们能感受到:好的算法不仅要“能跑通”,还要“高效、简洁”——冗余的操作不仅会降低效率,还会增加代码的维护成本和出错概率。
延伸练习:如果这道题要求「原地修改原数组」,如何优化空间复杂度?(提示:可以利用双指针,将合并后的区间直接覆盖原数组,空间复杂度可优化到O(1),感兴趣的可以尝试实现~)
最后,区间类题目有很强的规律性,比如LeetCode 56题(合并区间)、LeetCode 986题(区间列表的交集),核心思路都是“排序+双指针/遍历+边界判断”,掌握这道题的思路,能轻松应对同类题目。