【最长连续序列】
一、题目
给定一个未排序的整数数组 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
二、语法知识
2.1 集合set()
2.1.1 什么是集合 (set)?
集合 (set) 是 Python 中的一种内置数据类型,用于存储无序且不重复的元素集合。它类似于数学中的集合概念。
2.1.2 主要特性
- 无序性: 集合中的元素没有固定的顺序。每次打印或遍历集合时,元素的顺序可能不同。
- 唯一性: 集合会自动去除重复的元素。同一个元素在集合中只会出现一次。
- 元素要求: 集合中的元素必须是可哈希的。这通常意味着它们必须是不可变类型,如数字(整数、浮点数)、字符串、元组(仅包含不可变元素)等。列表和字典(可变类型)不能作为集合的元素。
- 可变性:
set本身是可变类型,可以添加或删除元素。Python 也提供了不可变的集合类型frozenset。
2.1.3 创建集合
- 使用花括号
{}:my_set = {1, 2, 3} print(my_set) # 输出可能是 {1, 2, 3}, {2, 1, 3} 等,顺序不确定 - 使用
set()构造函数:empty_set = set() # 创建空集合,注意 {} 创建的是空字典 from_list = set([1, 2, 2, 3]) # 从列表创建,去重后为 {1, 2, 3} from_string = set("hello") # 结果为 {'h', 'e', 'l', 'o'} (注意 'l' 只出现一次)
2.1.4 常用操作
- 添加元素:
my_set.add(4) # 添加元素 4 my_set.add(2) # 添加元素 2,但集合中已有,无变化 - 移除元素:
my_set.remove(3) # 移除元素 3。如果元素不存在,会引发 KeyError my_set.discard(3) # 移除元素 3。如果元素不存在,不报错 popped_element = my_set.pop() # 随机移除并返回一个元素。空集合会报错 my_set.clear() # 清空集合 - 成员检测:
if 2 in my_set:print("2 is in the set") - 集合大小:
size = len(my_set)
2.1.5 集合运算 (类比数学集合运算)
设两个集合 A={1,2,3}A = \{1, 2, 3\}A={1,2,3} 和 B={3,4,5}B = \{3, 4, 5\}B={3,4,5}。
- 并集 (
union()或|): 返回包含 AAA 和 BBB中所有唯一元素的新集合。
A∪B={1,2,3,4,5}A \cup B = \{1, 2, 3, 4, 5\}A∪B={1,2,3,4,5}union_set = A.union(B) # 或 A | B - 交集 (
intersection()或&): 返回同时存在于 AAA 和 BBB 中的元素的新集合。
A∩B={3}A \cap B = \{3\}A∩B={3}intersection_set = A.intersection(B) # 或 A & B - 差集 (
difference()或-): 返回在 AAA 中但不在 BBB 中的元素的新集合。
A−B={1,2}A - B = \{1, 2\}A−B={1,2}diff_set = A.difference(B) # 或 A - B - 对称差集 (
symmetric_difference()或^): 返回在 AAA 或 BBB 中,但不同时在两者中的元素的新集合。
A△B=(A−B)∪(B−A)={1,2,4,5}A \triangle B = (A - B) \cup (B - A) = \{1, 2, 4, 5\}A△B=(A−B)∪(B−A)={1,2,4,5}sym_diff_set = A.symmetric_difference(B) # 或 A ^ B - 子集/超集判断:
C = {1, 2} is_subset = C.issubset(A) # 或 C <= A, 检查 C 是否是 A 的子集 is_superset = A.issuperset(C) # 或 A >= C,检查 A 是否是 C 的超集 is_proper_subset = C < A # 检查 C 是否是 A 的真子集 (C != A)
2.1.6 集合推导式
类似于列表推导式,用于从可迭代对象创建集合:
squares_set = {x**2 for x in range(10)} # {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
2.1.7 不可变集合 (frozenset)
frozenset 是集合的不可变版本。创建后无法修改。它可以作为字典的键或另一个集合的元素。
immutable_set = frozenset([1, 2, 3])
2.1.8 应用场景
- 去重: 快速去除列表、字符串等中的重复元素。
unique_list = list(set(duplicate_list)) - 成员测试: 检查元素是否存在非常高效(平均时间复杂度接近 O(1)O(1)O(1))。
- 集合运算: 方便地进行并集、交集、差集等数学集合运算。
- 字典键的替代:
frozenset可以作为字典的键。
2.2 排序
2.2.1 使用内置的sorted()函数
sorted()函数可以对任何可迭代对象进行排序,返回一个新的已排序列表,不改变原数组。适用于列表、元组等数据类型。
示例代码:
arr = [3, 1, 4, 1, 5, 9, 2]
sorted_arr = sorted(arr)
print(sorted_arr) # 输出:[1, 1, 2, 3, 4, 5, 9]
2.2.2 使用列表的sort()方法
列表对象自带的sort()方法会直接修改原列表,不返回新列表,效率比sorted()更高。
示例代码:
arr = [3, 1, 4, 1, 5, 9, 2]
arr.sort()
print(arr) # 输出:[1, 1, 2, 3, 4, 5, 9]
2.2.3 降序排序
通过参数reverse=True可实现从大到小排序。
sorted()示例:
sorted_arr = sorted(arr, reverse=True)
sort()示例:
arr.sort(reverse=True)
2.2.4 对多维数组按特定条件排序
使用key参数指定排序依据。例如按子数组第二个元素排序:
arr = [[1, 4], [2, 3], [3, 2]]
sorted_arr = sorted(arr, key=lambda x: x[1]) # 输出:[[3, 2], [2, 3], [1, 4]]
2.2.5 使用NumPy库的sort()方法
对于NumPy数组,np.sort()返回排序后的新数组,而ndarray.sort()会原地修改数组。
示例代码:
import numpy as np
arr = np.array([3, 1, 4, 1, 5])
sorted_arr = np.sort(arr) # 返回新数组
arr.sort() # 原地排序
性能注意事项
- 对于纯Python列表,
sort()方法比sorted()更快,尤其适合大数组。 - NumPy的排序算法在数值型数据上效率显著高于纯Python方法。
2.3 获得字典中最大的值
方法一:使用max()函数直接获取值
通过max()函数结合字典的values()方法直接获取最大值:
my_dict = {'a': 10, 'b': 25, 'c': 5}
max_value = max(my_dict.values())
print(max_value) # 输出25
方法二:获取最大值对应的键值对
使用max()函数结合字典的items()方法,通过比较值来获取完整键值对:
max_pair = max(my_dict.items(), key=lambda x: x[1])
print(max_pair) # 输出('b', 25)
方法三:遍历字典比较值
手动遍历字典记录最大值,适合需要额外逻辑处理的场景:
max_val = float('-inf')
for value in my_dict.values():if value > max_val:max_val = value
print(max_val)
注意事项
- 字典值为非数字类型时需自定义比较逻辑(例如字符串按长度比较)。
- 空字典使用
max()会触发ValueError,需提前检查字典是否为空。
三、题解
3.1 题解一
思路:首先对列表进行从小到大排序,然后依次遍历每个元素,计算到该元素n(包括该元素)存在的最长连续序列长度记为L(n),采用字典记录(键为数n,值为L(n)),采用递归方法计算元素n+1的最长连续序列长度L(n=1)=L(n)+1,最后找到字典中最大的值LmaxL_{max}Lmax。
class Solution:def longestConsecutive(self, nums: List[int]) -> int:if not nums: # 处理空数组return 0num_set = set(nums) # 使用集合提高查找效率counts_dict = {}nums.sort() # 排序数组,确保顺序处理for n in nums:counts_dict[n]=1if n-1 in num_set:counts_dict[n]+=counts_dict[n-1] #累加前一个数的值(如果存在)return max(counts_dict.values())
输入:[100,4,200,1,3,2]
排序:[1,2,3,4,100,200]
集合:[1,2,3,4,100,200]
字典:{ }
对元素1:L(1)=1;
对元素2:L(2)=1+L(1)=2;
对元素3:L(3)=1+L(2)=3;
…
要点:
- 需单独处理空数组,否则寻找最大值max(counts_dict.values()出错;
- 使用集合set()查找(in)元素是否存在,以实现O(1)O(1)O(1)的查找,如果使用列表查找则时间复杂度为O(n)O(n)O(n);
- 当数组中有重复元素时,counts_dict[n] = 1会覆盖之前的值,然后重复计算元素n的L,虽然结果正确,但浪费了时间和空间;
3.2 题解二(力扣官方题解)
思路:哈希表。
枚举数组中的每个数 xxx,考虑以其为起点,不断尝试匹配 x+1x+1x+1,x+2x+2x+2,⋯\cdots⋯ 是否存在,假设最长匹配到了 x+yx+yx+y,那么以 xxx 为起点的最长连续序列即为 xxx,x+1x+1x+1,x+2x+2x+2,⋯\cdots⋯,x+yx+yx+y,其长度为 y+1y+1y+1,不断枚举并更新答案即可。
匹配过程,暴力的方法是 O(n)O(n)O(n) 遍历数组去看是否存在这个数,更高效的方法是用一个哈希表存储数组中的数,这样查看一个数是否存在即能优化至 O(1)O(1)O(1) 的时间复杂度。
如果已知有一个 x,x+1,x+2,⋯,x+yx, x+1, x+2, \cdots, x+yx,x+1,x+2,⋯,x+y 的连续序列,而从 x+1x+1x+1、x+2x+2x+2或 x+yx+yx+y处开始尝试匹配,得到的结果不会优于枚举 xxx为起点的答案。因此在外层循环时遇到这种情况可直接跳过。
由于我们要枚举的数 xxx 一定是在数组中不存在前驱数 x−1x−1x−1 的,不然会从 x−1x−1x−1 开始尝试匹配,因此每次在哈希表中检查是否存在 x−1x−1x−1 即能判断是否需要跳过了。
class Solution:def longestConsecutive(self, nums: List[int]) -> int:Max_L=0 #最大连续序列长度nums_set = set(nums) #去重for n in nums_set:if n-1 not in nums_set: #如果没有前驱数字n-1Current_L = 1 #当前元素为起点的最长连续序列长度Current_n = nwhile(Current_n+1 in nums_set):Current_L+=1Current_n+=1Max_L = max(Current_L,Max_L)return Max_L
