最长连续序列
Leetcode 做题笔记,就当作记录了。我的算法也不好,就不发到 Leetcode 评论,去误人子弟了。有问题也欢迎讨论,敬请斧正。
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
例如 nums = [100,4,200,1,3,2]
最长序列就是: 1 2 3 4 长度 4
想法是使用 hashMap,记录数组中每个数字对应的最长字符串长度。如果每个都遍历一边,就很啰嗦,需要尽量保存之前遍历过的结果。
很容易想到,当遍历到某个数字时候,使用前继数字长度加一,就无需遍历前面的数字了。如果数组都是升序,只需要遍历一边就可以了。存在降序情况,就需要更新之前的数据。这样才能保证,加一符合当前情况。
下面是我最开始想出来的算法:
// 最长连续序列
function main(arr) {
let max = 0; // 最大长度,默认空数组
const lengthMap = new Map();
for (let i = 0; i < arr.length; i++) {
let num = arr[i];
// 跳过重复项
if (lengthMap.has(num)) continue;
// 前继加一
let prevCount = lengthMap.has(num - 1) ? lengthMap.get(num - 1) + 1 : 1;
lengthMap.set(num, prevCount);
while (lengthMap.has(++num)) {
// 查找上一个count
// 降序部分,如果已处理过,不能丢失之前的数据
// 当前 count,可能在已计算过
let nextCount = lengthMap.get(num);
// 下一个数的 count 小于等于上一个
// 证明遍历中间有断层,以小的数字为准
prevCount = nextCount <= prevCount ? prevCount + 1 : nextCount;
lengthMap.set(num, prevCount);
}
max = max > prevCount ? max : prevCount; // 更新长度
}
return max;
}
大部分用例都过了,挂在了一个数据量比较大的用例上,超时了。
重新审视算法,发现有些浪费的地方。这个算法把每个数字对应的连续序列都计算了出来,实际上并没有必要。根据题目要求,只需要找到最大的序列就行了。这样就变成了找到每个序列最小的数字,从小往大查找一遍,再比较每个序列的大小就行了。
当前的遍历情况,不能确定当前数字是不是序列的最小值,每一次出现序列中的更小值,都要遍历一遍更新,降序的时间复杂度 O(n2)。
最简单的优化方式就是排序,排完序就不需要考虑升降序问题了。但是我这里写了 map,就还想沿着这个思路走下去。
map 的优化思路也很明确,想办法确定当前值是否为序列最小值。如果有简单办法检索,当前数组中是否有比当前值小 1 的数(例如,当前为 2,需要知道是否有 1 存在),那样就能保证一次把连续序列遍历完。
下面是优化后的代码,和官方答案思路是一致的(本来也是参考了官方答案):
function main(arr) {
let max = 0;
// 使用 Object 也可以
// 这里不需要存储 index 或者值
// 设置 Object 对应项 value 为 1 就行
// 遇到数字,Object 总要额外判断 0 很麻烦
// 所以我习惯用 map
const valueMap = new Map();
for (const val of arr) {
if (!valueMap.has(val)) {
// 重复值跳过
valueMap.set(val, 1);
}
}
// 遍历 map,map 已经做过去重
valueMap.forEach((_, val) => {
if (!valueMap.has(val - 1)) {
let len = 1;
let num = val;
// 最小值开始查找
// 同一条序列只会遍历一次
// 这样每个数字也只会遍历一次
while (valueMap.has(num + 1)) {
len++;
num++;
}
max = Math.max(max, len);
}
});
return max;
}