LeetCode 热题 100——哈希——最长连续序列

3. 最长连续序列

题目描述

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

请你设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入:nums = [100,4,200,1,3,2]

输出:4

解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。

示例 2:

输入:nums = [0,3,7,2,5,8,4,6,0,1]

输出:9

示例 3:

输入:nums = [1,0,1,2]

输出:3

提示:

0 <= nums.length <= 105

-109 <= nums[i] <= 109

求解

(1)去重 + 排序 + 遍历

javascript 复制代码
var longestConsecutive = function(nums) {
    if (nums.length === 0) return 0
    // 思路: 排序 + 遍历
    // 一开始没考虑到重复序列,对于重复序列 [1,2,2,3] -> 3,排序的时候要去重
    nums.sort((a, b) => a - b); 
    nums = [...new Set(nums)].sort((a, b) => a - b)
    let count = 0
    let max_count = 1 // 至少为1
    for(let i = 0; i < nums.length - 1; i++) {
        if(nums[i] + 1 === nums[i + 1]) {
            count++
            max_count = Math.max(max_count, count + 1)
        } else {
            count = 0
        }
    }
    return max_count
};

真的是习惯了复制粘贴的世界,自己敲代码百般漏洞(哭死);之前学的也全忘了,真的是每一处都是细节,虽然有点思路,但写出来s一样。

上面是自己的思路,也可用哈希求解

(2)使用Set

javascript 复制代码
var longestConsecutive = function(nums) {
    if (nums.length === 0) return 0;
    let set = new Set(nums) // 去重
    let ans = 1 // 至少有一个
    for (let item of set) {
        // 找最小值
        if(!set.has(item - 1)) {
            // 满足条件,则为 该数组中 某一连续序列的最小值
            let count = 0 // 计数器
            let cur = item // 记录之后的连续值
            while(set.has(cur + 1)) {
            // while(set.has(cur)) {
                count++
                cur++
            }
            // while循环结束,说明 某一段连续序列已经遍历完成
            ans = Math.max(ans, count + 1)
            // ans = Math.max(ans, count)
        }
    }
    return ans
}

(3)两种解法的时间复杂度分析:

  • 排序 + 遍历(O (N log N)
    • sort() 排序的时间复杂度是 O(N log N)(JavaScript 的 sort 用 Timsort 算法);
    • 后续遍历数组是 O(N),但整体被排序的 O(N log N) 主导。
    • 为什么慢:排序本身就是一个耗时的操作,尤其是数据量大的时候。
  • Set 查找(O (N)
    • 用 Set 存储数据,has() 查找是 O(1)(哈希表特性)
    • 外层 for 循环遍历所有元素(O (N)),但内层 while 循环不会重复遍历同一个连续序列(例如,找到 1 后,会一次性遍历 1,2,3,4,后续再遇到 2,3,4 时会直接跳过)。
    • 整体下来,所有元素只会被遍历一次,总时间复杂度 O(N)。
    • 为什么快:用空间换时间,避免了排序,改用哈希表快速查找来确定连续序列。

补充知识点

sort()

Array.prototype.sort() 默认按 "字符串 Unicode 编码顺序" 排序,而非数字大小。它会先将数组中所有元素转换为字符串,再逐个比较字符的 Unicode 编码值:

  • 转换后字符串为 ["100", "4", "200", "1", "3", "2"]
  • 比较规则:先比第一个字符的编码("1" < "2" < "3" < "4"),第一个字符相同时再比后续字符(如 "100" 与 "1",第一个字符都是 "1",但 "1" 长度更短,所以排在前面)。

因此,排序后的顺序是按字符串编码排列的,而非数字逻辑上的大小顺序。

  • 如何实现数字升序 / 降序?

    • 需手动传入比较函数 (a, b) => a - b(升序)或 (a, b) => b - a(降序):
    javascript 复制代码
    // 数字升序(正确排序)
    nums.sort((a, b) => a - b); 
    // 结果:[1, 2, 3, 4, 100, 200]
    
    // 数字降序
    nums.sort((a, b) => b - a); 
    // 结果:[200, 100, 4, 3, 2, 1]

去重 + 排序

  • Set 去重 + 排序(推荐)

    javascript 复制代码
    const nums = [100, 4, 200, 1, 3, 2, 2, 100];
    
    // 一行实现:去重 + 数字升序
    const result = [...new Set(nums)].sort((a, b) => a - b);
    
    console.log(result); // [1, 2, 3, 4, 100, 200]

    核心简化点:

    用扩展运算符 [...new Set(nums)] 直接将 Set 转为数组(替代 Array.from),代码更紧凑;

    排序时直接传入数字比较函数,无需额外变量。

  • filter 去重 + 排序

    javascript 复制代码
    const nums = [100, 4, 200, 1, 3, 2, 2, 100];
    
    // 简化为一行 filter + 排序
    const result = nums
      .filter((v, i, self) => self.indexOf(v) === i)
      .sort((a, b) => a - b);
    
    console.log(result); // [1, 2, 3, 4, 100, 200]

    去掉中间变量,直接链式调用 filter 和 sort; 箭头函数简化 filter 的回调逻辑。

  • Map 去重 + 排序(适合复杂场景)

    javascript 复制代码
    const nums = [100, 4, 200, 1, 3, 2, 2, 100];
    
    // 简化 Map 去重逻辑,一行转换为数组
    const result = Array.from(new Map(nums.map(v => [v, true])).keys())
      .sort((a, b) => a - b);
    
    console.log(result); // [1, 2, 3, 4, 100, 200]

    用 nums.map(v => [v, true]) 直接生成 Map 的键值对数组,替代 forEach 循环;

    链式调用 Array.from 和 sort,缩减代码行数。

总结:日常开发首选:[...new Set(nums)].sort((a, b) => a - b)(最简洁、性能最优)

== 和 ===

核心区别在于:是否进行类型转换。

  • === 严格相等 (Strict Equality)

    比较规则:

    先比较两边的值的 类型,如果类型不同,直接返回 false。

    如果类型相同,再比较它们的 值 是否相等。

    优点:行为可预测,不会产生意想不到的结果,是推荐的比较方式。

    示例:

    javascript 复制代码
    console.log(5 === 5);              // true (类型相同,值相同)
    console.log('5' === 5);            // false (类型不同:字符串 vs 数字)
    console.log(true === 1);           // false (类型不同:布尔值 vs 数字)
    console.log(null === undefined);   // false (类型不同:null vs undefined)
    console.log([] === []);            // false (类型相同,但引用的是不同的数组对象)
  • == 宽松相等 (Loose Equality)

    比较规则:

    先检查两边的值的类型,如果类型相同,就和 === 一样比较值。

    如果 类型不同,它会尝试进行 类型转换,将两边的值转换为同一个类型后再进行比较。

    缺点:类型转换的规则非常复杂,容易导致难以理解和调试的错误。

    示例:

    javascript 复制代码
    console.log(5 == 5);               // true (类型相同,值相同)
    console.log('5' == 5);             // true (字符串 '5' 被转换为数字 5)
    console.log(true == 1);            // true (布尔值 true 被转换为数字 1)
    console.log(null == undefined);    // true (这是一个特例,它们互相等于)
    console.log([] == '');             // true (数组 [] 转换为字符串是 '', 所以 '' == '' 为 true)
    console.log([] == 0);              // true (数组 [] -> 字符串 '' -> 数字 0)
    console.log('' == 0);              // true (字符串 '' 转换为数字 0)

    可以看到,== 的比较结果有时会让人感到困惑。

  • 核心区别对照表

特性 === 严格相等 == 宽松相等
类型检查 先比较类型,类型不同则为 false 类型不同时,会尝试将值转换为同一类型
结果可预测性 高,行为清晰 低,转换规则复杂,易出错
推荐使用场景 几乎所有场景 仅在某些特定、明确需要利用其类型转换规则的情况下使用
null vs undefined null === undefined 为 false null == undefined 为 true (唯一特例)

Set

Set 是一种内置对象,用于存储唯一值的集合 (即集合中的元素不会重复)。它的核心特点和用法如下:

1.基本概念

Set 是 ES6 新增的结构,以值 - 值 的形式存储数据(与 Map 的 "键 - 值" 不同),且元素唯一(重复添加同一值会被自动忽略)。

2.常用方法

方法 功能描述
new Set() 创建一个空的 Set 实例。
set.add(value) 向 Set 中添加一个值,返回 Set 本身(可链式调用)。
set.delete(value) 删除指定值,返回布尔值(成功删除为 true,否则 false)。
set.has(value) 判断 Set 中是否存在指定值,返回布尔值(true/false)。
set.clear() 清空 Set 中所有值。
set.size 只读属性,返回 Set 中值的数量。

3.遍历方式

Set 是可迭代对象,支持以下遍历方式:

  • for...of 遍历:for (let value of set) { ... }
  • set.keys():遍历所有值(与 set.values() 效果一致,因为 Set 是 "值 - 值" 结构)。
  • set.entries():遍历所有值的 "键值对"(但键和值相同,如 [1, 1])。

4.与数组的区别

特性 Set 数组(Array)
元素唯一性 元素唯一(重复值会被忽略) 允许重复元素
遍历便利性 原生支持迭代(for...of) 需手动遍历或用 forEach
去重操作 天生去重,直接创建 Set 即可 需借助 filter 或 Set 转换(如 [...new Set(arr)])

5.代码示例

javascript 复制代码
// 创建 Set
const set = new Set();

// 添加值(可链式调用)
set.add(1).add(2).add(2); // 重复的 2 会被忽略
console.log(set); // Set { 1, 2 }

// 检查是否存在值
console.log(set.has(2)); // true
console.log(set.has(3)); // false

// 删除值
set.delete(1);
console.log(set); // Set { 2 }

// 遍历
for (let value of set) {
  console.log(value); // 2
}

// 转换为数组(常用去重方式)
const arr = [1, 2, 2, 3];
const uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // [1, 2, 3]

6.典型应用场景

  • 数组去重:[...new Set(arr)] 是最简洁的数组去重方式。
  • 判断元素是否重复:利用 Set 的唯一性,可快速检查数据中是否存在重复项。
  • 交集、并集、差集计算:结合 Set 的遍历和方法,可高效实现集合运算(如 const intersection = new Set([...set1].filter(x => set2.has(x))))。
相关推荐
Dream it possible!2 小时前
LeetCode 面试经典 150_二叉树_二叉树展开为链表(74_114_C++_中等)
c++·leetcode·链表·面试·二叉树
做怪小疯子2 小时前
LeetCode 热题 100——双指针——三数之和
算法·leetcode·职场和发展
高山上有一只小老虎3 小时前
等差数列前n项的和
java·算法
sin_hielo3 小时前
leetcode 2536
数据结构·算法·leetcode
flashlight_hi3 小时前
LeetCode 分类刷题:203. 移除链表元素
算法·leetcode·链表
py有趣3 小时前
LeetCode算法学习之数组中的第K个最大元素
学习·算法·leetcode
吗~喽3 小时前
【LeetCode】将 x 减到 0 的最小操作数
算法·leetcode
what_20183 小时前
list集合使用
数据结构·算法·list
hetao17338374 小时前
2025-11-13~14 hetao1733837的刷题记录
c++·算法