LeetCode简单难度的经典区间题------228. 汇总区间,这道题是区间处理的入门必刷款,核心考察「双指针遍历」和「边界场景处理」,代码不长但细节拉满,新手也能快速上手,话不多说,直接开干!
一、题目解读(原题复刻+重点提炼)
先看原题要求,帮大家提炼关键信息,避免踩坑:
✨ 关键提醒(题目隐含条件,决定解题方向)
-
数组是「有序」的:无需额外排序,直接遍历即可判断连续区间;
-
无重复元素:不用考虑去重,只需关注"当前元素与前一个元素是否连续";
-
最小有序区间:每个区间必须是"连续递增1"的,不能跳过元素,也不能包含数组中没有的数字。
举个直观例子,帮大家理解需求:
-
输入 nums = [0,1,2,4,5,7] → 输出 ["0->2","4->5","7"]
-
输入 nums = [1] → 输出 ["1"]
-
输入 nums = [] → 输出 [](边界场景,容易忽略)
二、解题思路(核心:双指针划定连续区间)
这道题的核心逻辑很简单:用两个指针(start 和 end),动态标记当前连续区间的「起始下标」和「结束下标」,遍历数组时判断区间是否断开,断开则格式化当前区间,再重置指针,继续寻找下一个连续区间。
具体步骤拆解(图文结合更易理解,新手可跟着走):
-
初始化:定义结果数组 res(存储最终格式化的区间字符串),定义 start 和 end 指针,初始都指向数组第0个元素(即 start = end = 0),表示初始区间从第一个元素开始。
-
遍历数组:从第二个元素(i=1)开始遍历,因为要和前一个元素(i-1)比较,判断是否连续。
-
判断区间是否断开:若当前元素 nums[i] 与前一个元素 nums[i-1] 的差值 > 1,说明当前区间断开(前一个区间结束,新的区间开始)。
-
格式化当前区间:若 start == end(说明当前区间只有一个元素),直接将该元素转为字符串存入 res;若不相等,格式化为 "start->end" 存入 res。
-
重置指针:将 start 和 end 都指向当前下标 i,开始标记新的连续区间。
-
区间未断开:若差值 ≤ 1(题目中无重复元素,差值只能是1),说明当前元素仍在连续区间内,只需将 end 指针更新为 i,扩大当前区间。
-
处理最后一个区间:遍历结束后,最后一个区间还未存入 res(因为循环中只处理"断开时"的区间),需单独格式化并存入 res。
-
返回结果:返回 res 数组。
💡 小技巧:之所以用下标指针(而非直接存数值),是因为数组有序无重复,通过下标访问数值更灵活,也能避免额外存储数值的空间消耗。
三、完整代码+逐行拆解
先贴出可直接提交的完整代码(TypeScript 版本,JavaScript 可直接删除类型注解使用),再逐行拆解关键细节,新手也能看懂每一步的作用:
typescript
function summaryRanges(nums: number[]): string[] {
const res: string[] = [];
// 边界处理:空数组直接返回空结果(容易忽略的坑)
if (nums.length === 0) return res;
let start = 0;
let end = 0;
for (let i = 1; i < nums.length; i++) {
// 核心判断:当前元素与前一个元素差值>1 → 区间断开
if (nums[i] - nums[i - 1] > 1) {
// 格式化当前区间,存入结果
if (start === end) {
res.push(nums[start].toString());
} else {
res.push(`${nums[start]}->${nums[end]}`);
}
// 重置指针,开始新的区间
start = i;
end = i;
} else {
// 区间未断开,扩大当前区间的结束位置
end = i;
}
}
// 处理最后一个区间(循环结束后未处理)
if (start === end) {
res.push(nums[start].toString());
} else {
res.push(`${nums[start]}->${nums[end]}`);
}
return res;
};
逐行拆解(重点标注易踩坑点)
-
const res: string[] = [];:初始化结果数组,用于存储最终格式化的区间字符串(类型注解可省略,不影响运行)。 -
if (nums.length === 0) return res;:边界处理重点!若输入为空数组,直接返回空结果,避免后续访问 nums[start] 出现 undefined 错误(很多新手会忽略这个场景,导致提交失败)。 -
let start = 0; let end = 0;:定义双指针,初始都指向数组第一个元素(下标0),标记当前连续区间的起始和结束位置。 -
for (let i = 1; i < nums.length; i++):从第二个元素(下标1)开始遍历,因为要和前一个元素(i-1)比较,判断是否连续。 -
if (nums[i] - nums[i - 1] > 1):核心判断条件------当前元素与前一个元素的差值大于1,说明两个元素不连续,前一个区间结束,需要格式化并存储。 -
内部判断
if (start === end) { ... } else { ... }:根据指针是否相等,判断当前区间是"单个元素"还是"连续区间",并按题目要求格式化存入 res。 -
start = i; end = i;:区间断开后,重置双指针,指向当前元素(i),开始标记新的连续区间。 -
else { end = i; }:若区间未断开(差值为1),只需更新 end 指针,将当前元素纳入当前连续区间,start 指针保持不变。 -
循环外部的
if...else...:第二个易踩坑点!循环结束后,最后一个连续区间还未存入 res(因为只有"区间断开"时才会存入),所以需要单独处理,逻辑和循环内部的格式化逻辑一致。 -
return res;:返回最终的区间字符串数组,完成解题。
四、测试用例(覆盖所有场景,提交前必测)
提交代码前,建议测试以下4个核心场景,确保代码无bug(覆盖普通场景、边界场景、特殊场景):
| 输入数组 nums | 预期输出 | 场景说明 |
|---|---|---|
| [0,1,2,4,5,7] | ["0->2","4->5","7"] | 普通场景:多连续区间+单个元素 |
| [] | [] | 边界场景:空数组 |
| [1] | ["1"] | 边界场景:单元素数组 |
| [-1,0,2,3,4] | ["-1->0","2->4"] | 特殊场景:包含负数+连续区间 |
五、代码优化思路(可选,提升可读性)
上面的代码逻辑正确、可直接提交,但有一个小问题:「区间格式化」的逻辑重复了两次(循环内部和循环外部)。我们可以将格式化逻辑抽离成一个单独的函数,让代码更简洁、易维护。
优化后代码:
typescript
function summaryRanges(nums: number[]): string[] {
const res: string[] = [];
if (nums.length === 0) return res;
let start = 0;
let end = 0;
for (let i = 1; i < nums.length; i++) {
if (nums[i] - nums[i - 1] > 1) {
// 调用格式化函数,减少重复代码
res.push(formatRange(nums[start], nums[end]));
start = i;
end = i;
} else {
end = i;
}
}
// 最后一个区间也调用格式化函数
res.push(formatRange(nums[start], nums[end]));
return res;
}
// 抽离区间格式化逻辑,单独封装
function formatRange(startNum: number, endNum: number): string {
return startNum === endNum ? startNum.toString() : `${startNum}->${endNum}`;
}
优化点说明:
-
新增
formatRange函数,专门处理区间格式化,输入起始值和结束值,返回符合题目要求的字符串; -
主函数中两次格式化操作,均改为调用该函数,减少代码冗余,后续若需修改格式化规则(如修改连接符),只需修改一个地方即可。
六、解题总结(核心考点+新手提醒)
这道题虽然是简单题,但覆盖了算法入门的核心考点,也是面试中常考的"送分题",总结几点关键收获:
-
核心考点:双指针遍历 (动态划定区间)、边界场景处理 (空数组、单元素数组)、字符串格式化;
-
新手避坑:一定要处理空数组场景,否则会出现 undefined 错误;循环结束后,别忘了处理最后一个区间;
-
延伸思考:这道题是「区间处理」的入门题,后续的区间合并、区间插入等中等难度题目,核心逻辑也离不开"双指针划定区间",掌握这道题的思路,能为后续刷题打下基础。