pyhton(大厂笔试/面试)最长子序列(哈希-回溯-中等)含源码(二十三)
一、题目(含示例)
问题描述:给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度,设计算法解决此问题(第二个解法需满足时间复杂度 O (n))。
示例:
- 输入:
nums = [100,4,200,1,3,2]
,输出:4(最长序列为[1,2,3,4]
)。 - 输入:
nums = [0,3,7,2,5,8,4,6,0,1]
,输出:9(最长序列为[0,1,2,3,4,5,6,7,8]
)。 - 输入:
nums = [1,0,1,2]
,输出:3(最长序列为[0,1,2]
)。
二、解题关键(难点和核心逻辑)
难点
- 如何高效判断 “数字连续性”(避免重复计算或遗漏序列)。
- 平衡时间复杂度:回溯法需枚举所有可能序列(时间复杂度高),哈希表法需通过快速查找优化效率(满足 O (n))。
核心逻辑
回溯法(解法 1):
- 先对数组去重、排序(确保连续数字相邻),通过递归 + 回溯枚举所有可能的连续序列,实时更新最长序列长度。
- 核心:对每个元素,要么 “延续当前序列”(若下一个元素是当前最后元素 + 1),要么 “开启新序列”,通过回溯遍历所有可能性。
哈希表法(解法 2):
- 利用集合(哈希表)的 O (1) 查找特性,仅对 “连续序列的起点”(即
num-1
不在集合中)向后延伸,计算序列长度,避免重复处理同一序列。 - 核心:通过判断 “是否为起点” 减少无效计算,每个元素仅被访问一次,保证 O (n) 时间复杂度。
- 利用集合(哈希表)的 O (1) 查找特性,仅对 “连续序列的起点”(即
三、完整代码实现
解法 1:回溯法(不考虑时间复杂度)
from typing import Listclass Solution:def longestConsecutive(self, nums: List[int]) -> int:if not nums: # 处理空数组return 0max_length = 1 # 初始化最长长度(至少为1)# 去重+排序:确保连续数字相邻,避免重复计算nums = sorted(list(set(nums)))current = [nums[0]] # 初始序列从第一个元素开始def backtrack(start, current):nonlocal max_length # 允许修改外部变量max_length# 更新最长长度current_len = len(current)if current_len > max_length:max_length = current_len# 递归终止:遍历完所有元素if start == len(nums):return# 遍历剩余元素,尝试延续或开启新序列for i in range(start, len(nums)):# 若当前元素可延续序列(当前最后元素+1)if current[-1] + 1 == nums[i]:current.append(nums[i]) # 加入序列backtrack(i + 1, current) # 递归下一个元素current.pop() # 回溯:恢复状态# 开启新序列(从当前元素开始)new_current = [nums[i]]backtrack(i + 1, new_current)# 从索引1开始遍历,初始序列为[nums[0]]backtrack(1, current)return max_length
解法 2:哈希表法(时间复杂度 O (n))
from typing import Listclass Solution:def longestConsecutive(self, nums: List[int]) -> int:if not nums: # 处理空数组return 0num_set = set(nums) # 转为集合:去重+O(1)查找max_length = 1 # 初始化最长长度for num in num_set:# 仅当num是序列起点(num-1不在集合中)时,向后延伸if num - 1 not in num_set:current_num = num # 当前序列的最后一个数字current_length = 1 # 当前序列长度(初始为1)# 向后查找连续数字,更新长度while current_num + 1 in num_set:current_num += 1current_length += 1# 更新最长长度max_length = max(max_length, current_length)return max_length
四、解题基础知识(代码中函数 / 概念详解)
列表与集合转换:
set(nums)
:将列表转为集合,自动去重(集合元素唯一),时间复杂度 O (n)。list(set(nums))
:将集合转回列表(集合无序,需配合排序使用),时间复杂度 O (n)。
排序函数
sorted()
: 降序排列: sorted(nums,reverse=True)- 对列表升序排序,时间复杂度 O (n log n),确保连续数字在列表中相邻(如
[3,1,2]
排序后为[1,2,3]
)。
- 对列表升序排序,时间复杂度 O (n log n),确保连续数字在列表中相邻(如
回溯法核心概念:
- 递归:函数自身调用以探索子问题(如
backtrack(i+1, current)
探索下一个元素)。 - 状态恢复(
current.pop()
):在递归返回后移除最后添加的元素,确保后续遍历不被干扰(关键特性)。 nonlocal
关键字:允许内部函数(backtrack
)修改外部函数(longestConsecutive
)中的变量(max_length
)。
- 递归:函数自身调用以探索子问题(如
集合的查找操作:
num in num_set
:判断元素是否在集合中,时间复杂度 O (1)(哈希表特性),是解法 2 高效的核心。
循环控制:
for
循环:遍历集合 / 列表元素(解法 1 中遍历剩余元素,解法 2 中遍历所有去重元素)。while
循环:解法 2 中用于向后延伸连续序列(如从1
延伸到2,3,4
)。
五、进阶知识
时间复杂度对比:
- 回溯法:排序占 O (n log n),回溯过程枚举所有可能序列,时间复杂度 O (2ⁿ)(指数级),仅适用于小规模数据(n≤20)。
- 哈希表法:集合转换 O (n),遍历 + 查找 O (n)(每个元素最多被访问 2 次),总时间 O (n),适用于大规模数据(n≤10⁵)。
空间复杂度对比:
- 回溯法:递归栈深度 O (n),存储序列需 O (n),总空间 O (n)。
- 哈希表法:集合存储 O (n),无额外递归空间,总空间 O (n)。
哈希表的优势:
- 查找、插入、删除操作均为 O (1),适合 “频繁判断元素是否存在” 的场景(如连续序列问题)。
回溯法的局限性:
- 时间复杂度高,无法处理大规模数据;但适合 “枚举所有可能解” 的场景(如组合、排列问题)。
连续序列问题的本质:
- 核心是找到 “序列起点”(前一个元素不存在),再延伸计算长度,避免重复处理(两种解法均体现此思路,只是实现方式不同)。
六、提炼解题套路和模板
套路 1:回溯法(枚举所有可能序列)
适用场景:小规模数据、需枚举所有可能解的问题(如组合、连续序列枚举)。
步骤:
- 预处理数据(去重、排序,减少无效计算)。
- 定义递归函数,参数包括 “当前位置” 和 “当前序列”。
- 递归逻辑:
- 终止条件:遍历完所有元素。
- 选择逻辑:对每个元素,要么 “延续当前序列”(满足连续条件),要么 “开启新序列”。
- 回溯:修改状态后递归,返回后恢复状态。
- 实时更新最优解(如最长长度)。
套路 2:哈希表法(高效查找 + 起点延伸)
适用场景:大规模数据、需 O (n) 时间复杂度的问题(如连续序列、两数之和)。
步骤:
- 将数组转为集合(去重 + O (1) 查找)。
- 遍历集合,筛选 “序列起点”(
num-1
不在集合中)。 - 对每个起点,向后延伸计算连续序列长度(
num+1, num+2,...
)。 - 记录最长长度,返回结果。
总结:连续序列问题优先用哈希表法(高效),小规模数据或需枚举所有解时用回溯法。核心是通过 “起点判断” 减少无效计算,利用数据结构特性(集合的快速查找)优化效率。