Reading

哈希表

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

示例 3:

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

提示:

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

题解

我们需要在 \(O(1)\) 的时间内查找某个数是否存在。因此,首先将数组中的所有元素放入一个 HashSet 中。这不仅能去重,还能支持快速查找。

避免冗余计算 (关键优化)

如果我们对集合中的每一个数都尝试去向后计数(例如,对于 x,尝试找 x+1, x+2...),最坏情况下的时间复杂度会退化到 \(O(n^2)\)

优化策略
我们只从序列的起点开始计数
对于集合中的元素 x,如果 x - 1 也在集合中,说明 x 并不是一个连续序列的起点(x-1 才是更前面的元素),我们可以直接跳过 x
只有当 x - 1 不在 集合中时,我们才开始向后匹配 x + 1, x + 2 等等。

算法步骤

  1. 初始化:将 nums 转化为 HashSet(去重且查找快)。
  2. 遍历:遍历 HashSet 中的每一个元素 num
  3. 判断起点:检查 num - 1 是否存在于集合中。
    • 如果存在:说明 num 不是起点,跳过。
    • 如果不存在:说明 num 是序列的起点。
  4. 向后延伸:从 num 开始,不断检查 num + 1num + 2 是否存在,直到断裂。记录当前序列长度。
  5. 更新最大值:维护一个全局变量 longest_streak,记录遇到的最长长度。
class Solution:
    def longestConsecutive(self, nums: List[int]) -> int:
        # 1. 将数组转换为集合,去重并实现 O(1) 查找
        num_set = set(nums)
        longest_streak = 0

        # 2. 遍历集合中的每个数
        for num in num_set:
            # 3. 只有当 num 是序列的起点时(即 num-1 不在集合中),才开始计数
            if num - 1 not in num_set:
                current_num = num
                current_streak = 1

                # 4. 向后寻找连续的数字
                while current_num + 1 in num_set:
                    current_num += 1
                    current_streak += 1

                # 5. 更新最大长度
                longest_streak = max(longest_streak, current_streak)

        return longest_streak

41. 缺失的第一个正数

题目

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

示例 1:

输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。

示例 2:

输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。

示例 3:

输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现。

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1

题解

分析: 原地哈希

  1. 遍历数组,将所有小于等于0或大于len(nums)的数替换为len(nums)+1因为这些数对结果没有影响
  2. 再次遍历数组,对于每个数num,计算其绝对值abs_num为什么进行绝对值操作呢?因为在后续的标记过程中,我们会将某些位置的数变为负数
    • 如果1 <= abs_num <= len(nums),则将nums[abs_num - 1]标记为负数,表示abs_num存在于数组中
      为什么要这么做呢?因为数组长度为\(n\),最小的缺失正整数只能在\(1\)\(n+1\)之间,所以我们只需要关注\(1\)\(n\)的数,所以将不在这个范围内的数替换掉,通过将对应位置的数标记为负数,我们可以在后续的遍历中快速判断某个数是否存在于数组中
  1. 最后遍历数组,找到第一个正数的位置i,则i+1即为缺失的最小正整数
class Solution(object):
    def firstMissingPositive(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        n = len(nums)
        for i in range(n):
            if nums[i] <= 0 or nums[i] > n:
                nums[i] = n + 1
        for i in range(n):
            abs_num = abs(nums[i])
            if 1 <= abs_num <= n:
                nums[abs_num - 1] = -abs(nums[abs_num - 1])
        for i in range(n):
            if nums[i] > 0:
                return i + 1
        return n + 1