汇总区间怎么把有序数组压缩成连续范围?(LeetCode 228)
简化版
汇总区间用一次线性扫描。因为数组有序且无重复,连续段满足 nums[i] == nums[i-1] + 1;遇到不连续的位置就收尾上一段,最后别忘了把末尾段加入答案。
详细版
设 start 表示当前连续段起点,遍历数组下标 i。如果 i+1 越界,或 nums[i+1] != nums[i] + 1,说明当前连续段在 i 结束,需要输出 [start, nums[i]]。如果起点等于终点,输出单个数字;否则输出 "start->end"。
这题看起来简单,但面试常考两个细节:第一,数组可能为空;第二,nums[i] + 1 可能在整型最大值处溢出。更稳的写法是用 long 比较,或改写成 nums[i+1] - nums[i] == 1 时也要注意差值溢出。Java 中可以转 long 后判断。
时间复杂度 O(n),除输出外额外空间 O(1)。本质是区间问题里的“从点生成区间”,和合并区间相反。
完整版教学
一、题目本质是从点集生成区间
输入是一个升序、无重复的整数数组,输出要把连续的数字压缩成区间。比如 [0,1,2,4,5,7] 应变成 ["0->2","4->5","7"]。
这类题的核心不是排序,因为输入已经有序;核心是识别“连续段什么时候结束”。只要连续关系被打断,就可以把前面那段输出。
记忆钩子:汇总区间是在“点”上做扫描,遇到断点就把上一段封口。
二、连续段的判定条件
对相邻两个数 a=nums[i]、b=nums[i+1],如果 b == a + 1,它们属于同一个连续段;否则当前段在 i 结束。
nums = [0, 1, 2, 4, 5, 7]
0--1--2 4--5 7
输出: [0,2], [4,5], [7,7]
因为数组无重复且升序,连续关系只需要检查相邻元素。不需要回头,也不需要维护复杂结构。
三、扫描流程怎么设计
常见写法是维护一个 start,表示当前段的起点。遍历每个下标 i,当发现 i 是当前段末尾时,输出 start..nums[i],再把下一个位置设成新段起点。
start = nums[0]
for i in [0..n-1]:
if i == n-1 or nums[i+1] != nums[i] + 1:
emit(start, nums[i])
if i + 1 < n: start = nums[i+1]
这个流程把“最后一段”统一放进循环,不需要循环结束后再写一段额外逻辑,也就少一个漏加尾段的风险。
四、代码模板
List<String> summaryRanges(int[] nums) {
List<String> ans = new ArrayList<>();
if (nums.length == 0) return ans;
int start = nums[0];
for (int i = 0; i < nums.length; i++) {
boolean endOfRange = i == nums.length - 1
|| (long) nums[i + 1] != (long) nums[i] + 1;
if (endOfRange) {
if (start == nums[i]) ans.add(String.valueOf(start));
else ans.add(start + "->" + nums[i]);
if (i + 1 < nums.length) start = nums[i + 1];
}
}
return ans;
}
这里用 (long) nums[i] + 1 防止 Integer.MAX_VALUE + 1 溢出。虽然很多测试数据不触发,但面试中主动提这点说明边界意识到位。
五、数字例子手推
以 [0,1,2,4,5,7] 为例:
| i | nums[i] | 下一个数 | 是否断开 | 输出 |
|---|---|---|---|---|
| 0 | 0 | 1 | 否 | - |
| 1 | 1 | 2 | 否 | - |
| 2 | 2 | 4 | 是 | 0->2 |
| 4 | 5 | 7 | 是 | 4->5 |
| 5 | 7 | 越界 | 是 | 7 |
输出单点时不能写成 "7->7",题目要求单个数字单独显示。这是格式题的关键细节。
六、和合并区间的关系
| 题型 | 输入 | 核心动作 | 输出 |
|---|---|---|---|
| 汇总区间 | 有序点集 | 连续点压缩成段 | 不重叠区间字符串 |
| 合并区间 | 若干区间 | 重叠段合并 | 不重叠区间数组 |
| 插入区间 | 有序不重叠区间 + 新区间 | 三段扫描 | 更新后的区间数组 |
汇总区间是区间专题的基础题,它训练的是边界收尾。很多复杂题也要先能稳定维护“当前段起点”和“当前段终点”。
七、常见误区与追问
- 误区:最后一段会自动输出。 如果只在断点处输出,末尾没有下一个断点,必须用
i == n-1统一处理。 - 误区:单点区间也输出 start->end。 题目要求起点等于终点时只输出一个数字。
- 误区:直接写 nums[i] + 1 不考虑溢出。 当
nums[i]是最大整数时会溢出,转 long 更稳。 - 追问:如果数组不是有序的怎么办? 需要先排序并去重,但这会改变题目复杂度和语义。
- 追问:如果允许重复数字怎么办? 重复数字不表示连续前进,需要先决定重复是否跳过,再维护区间。
- 追问:为什么时间是 O(n)? 每个元素只被扫描一次,输出总长度不计入额外算法成本时是线性的。
八、加强记忆
汇总区间记住“起点 start + 断点收尾”。相邻差 1 就继续,不连续或到数组末尾就输出当前段;单点输出数字,多点输出 start->end。边界上重点防空数组、末尾漏加和整数溢出。