通过一道算法题,学会运用哈希表和Set

42 阅读4分钟

闲来无事,写一道算法题,竟有这么大收获。

128. 最长连续序列

题目难度: 中等

给定一个未排序的整数数组 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

 

提示:

  • 0 <= nums.length <= 105
  • -109 <= nums[i] <= 109

解题过程

解题思路一:哈希表

我们都知道,哈希表通过键(key)来访问记录值(value),易知key存数字,那么value存什么呢?

value存自己处在的连续序列的长度。

  • 遍历nums数组
  • 重复的数字跳过
  • 获取当前数字(key)的左邻居所在序列的长度(value)
  • 获取当前数字(key)的右邻居所在序列的长度(value)
  • 将新序列的长度存入自己的value里
  • 更新最长连续序列值
  • 更新新序列的左端数字的value
  • 更新新序列的右端数字的value

解题代码

/**
 * @param {number[]} nums
 * @return {number}
 */

var longestConsecutive = function(nums) {
    let max = 0;
    let map = new Map()
    for(let i = 0; i < nums.length; i++){
        if (!map.has(nums[i])) {
            let left = map.get(nums[i] - 1) || 0
            let right = map.get(nums[i] + 1) || 0
            let cur = left + right + 1
            map.set(nums[i], cur)
            max = Math.max(max, cur)
            map.set(nums[i] - left, cur)
            map.set(nums[i] + right, cur)
        }
    }
    return max;
};

在介绍第二种方法之前,我们先认识第二种方法要用到的Set数据结构。

Set介绍

在JavaScript中,Set 是ES6引入的一种新的数据结构,它类似于数组,但是成员的值都是唯一的,没有重复的值。Set 对象用来存储唯一的值的集合,这些值可以是任何类型的,包括对象和原始类型。

以下是一些关于 Set 的关键点:

  1. 创建 Set: 可以通过 new Set() 来创建一个新的 Set 对象,也可以传入一个可迭代的对象(如数组)来初始化 Set
  2. 添加元素: 使用 add() 方法来向 Set 添加元素。
  3. 删除元素: 使用 delete() 方法从 Set 中移除元素。
  4. 检查元素: 使用 has() 方法来检查 Set 是否包含某个元素。
  5. 获取大小: Set 提供了 size 属性来返回当前 Set 中的元素数量。
  6. 迭代 Set: 可以使用 for...of 循环或 forEach() 方法来迭代 Set 中的元素。
  7. 转换为数组: 可以使用扩展运算符 ...Array.from() 来将 Set 转换为数组。
// 创建一个空的 Set
let s = new Set();

// 向 Set 中添加元素
s.add(1);
s.add(2);
s.add(3);
s.add(2); // 这个 2 不会被添加,因为已经存在

// 检查 Set 是否包含某个元素
console.log(s.has(2)); // true
console.log(s.has(4)); // false

// 从 Set 中删除元素
s.delete(2);

// 获取 Set 的大小
console.log(s.size); // 2

// 迭代 Set
s.forEach(value => console.log(value));

// 将 Set 转换为数组
let arr = [...s];
console.log(arr); // [1, 3]

解题思路二:Set

由于查找 Set 中的元素的时间复杂度是 O(1),所以这次我们使用不同于解题思路一的解法:

  • 确定是否为左起点,
  • 若非,则跳过;
  • 若是,则不断遍历当前数字的右一位,直到set里不存在右一位,说明该序列已排完。

换言之:

  • 查找 Set 中的元素的时间复杂度是 O(1),JS 的 Set 能给数组去掉重复元素
  • 将数组元素存入 set 中,遍历数组 nums
  • 如果 当前项 - 1 存在于 set ,说明当前项不是连续序列的起点,跳过,继续遍历
  • 当前项没有“左邻居”,它就是连续序列的起点
  • 不断在 set 中查看 cur + 1 是否存在,存在,则 count +1
  • cur 不再有 “右邻居” 了,就算出了一段连续序列的长度

解题代码

var longestConsecutive = (nums) => {
  const set = new Set(nums) // set存放数组的全部数字
  let max = 0
  for (let i = 0; i < nums.length; i++) {
    if (!set.has(nums[i] - 1)) { // nums[i]没有左邻居,是序列的起点
      let cur = nums[i]
      let count = 1
      while (set.has(cur + 1)) { // cur有右邻居cur+1
        cur++ // 更新cur
        count++ 
      }
      max = Math.max(max, count) // cur不再有右邻居,检查count是否最大
    }
  }
  return max
}

亦可使用Set去除重复元素,再模仿解题思路一的做法做完剩下流程。

如有错误之处,敬请大佬留言改正。