题目来源:改编自 LeetCode 128. 最长连续序列。本 Lab 使用课程自定义输入输出协议和独立测试,不复制来源站点的代码或测试。
学习目标
前置知识与环境
先阅读第 9.2 节 散列表的"散列表与有序结构"取舍小节("散列表与有序结构的取舍"),理解散列集合换来
在 Lab 目录执行 make doctor 检查环境;未安装 Make 时可在仓库根使用 pnpm 兜底命令(见下文"运行")。
任务
给定一个长度为 n 的整数数组(无序、可重复),求其中最长的"连续整数段"的长度:段内的值在值域上恰好依次相差 1(如 1,2,3,4),段中元素在数组里的位置与先后顺序无关;重复值不影响段长。
例如 100, 4, 200, 1, 3, 2 中存在连续段 1, 2, 3, 4,答案为 4。
输入格式
- 第一行:一个整数
n; - 第二行:
n个整数,依次为数组元素。
输出格式
一行一个整数:最长连续整数段的长度。
数据范围与限制
| 项目 | 范围 |
|---|---|
元素个数 n | 1 ≤ n ≤ 2×10⁵ |
元素值 a | −10⁹ ≤ a ≤ 10⁹(可重复) |
| 时间复杂度要求 | 散列集合 |
| 空间限制 | 值域跨度达 2×10⁹,不允许开与值域同级的布尔数组,只能以"存值"的方式记录出现过的元素 |
正常、边界与错误情况
- 正常:多段并存时取最长;重复值只按一个计数;
- 边界:
n = 1时答案为1;全部元素相同(去重后只有一段长 1);段跨越负数与 0(如−2,−1,0,1);段跨±10⁹端点附近; - 常见错误:把"数组下标相邻"当成"值连续"——本题数组乱序,位置相邻的两个元素未必相差 1;对每个值都向左、右两个方向盲目扩展会重复访问同一批值,退化为平方级。
样例
样例 1
input
6
100 4 200 1 3 2output
4数组中出现的值构成连续段 1,2,3,4(长度 4)、100(长度 1)与 200(长度 1),最长为 4。注意 4 和 100 在数组里位置相邻,但值不连续——这与下标无关。
样例 2
input
10
-2 -1 0 1 7 8 100 -2 -1 5output
4值去重后为 {−2,−1,0,1,5,7,8,100}:段 −2,−1,0,1 长度 4,跨过负数与 0;7,8 长度 2;5、100 各长 1。重复出现的 −2,−1 不改变段长,答案为 4。
实现提示
- 把所有值放进散列集合,支持
的"存在性"查询,重复值自动合并; - 对集合中的每个值
v,先看v−1是否在集合中:在,说明v不是某段的起点,直接跳过;不在,v就是一个段起点,从它开始向右数v+1, v+2, ...直到断开; - 复杂度为什么是
:每个值只会在"从自己所在段的起点向右数"时被访问一次,起点判断本身也只有一次查询; - 陷阱:如果不做"没有前驱"判断、对每个值都向两边扩展,同一个长段会被反复数多遍,最坏
。
运行
powershell
make run
# 免 Make 兜底:在仓库根执行
pnpm lab:run -- labs/chapter-09/exercise/E-09-08-longest-consecutive-sequence需要单用例或严格评分时使用 make run CASE=006-stress-2e5 与 make score;也可用 pnpm lab:score -- labs/chapter-09/exercise/E-09-08-longest-consecutive-sequence 兜底。
完成清单
思考与复盘
- 如果去掉"v−1 不在集合中才扩展"的判断,最坏情况(如整段
1..n倒序输入)会多花多少时间?用大 O 与具体计数说明。 - 排序解法
与散列解法 :当后续还需要"按值有序访问"或"区间报告"时,哪种更合算?当只需要答案时呢? - 值域跨度 2×10⁹ 时为什么不能直接开布尔数组?散列集合用多少空间就够?这与"装填因子"有什么关系?