给定一个未排序的整数数组 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
这个问题可以通过使用哈希表来解决。我们可以利用哈希表来记录每个数字是否出现过,以及它之前和之后的数字是否出现过。具体步骤如下:
- 将数组中的所有数字放入哈希表中,同时记录每个数字的连续序列长度。
- 对数组进行排序,这样我们可以更容易地找到连续的数字。
- 遍历排序后的数组,对于每个数字,检查它之前和之后的数字是否在哈希表中出现过。如果是,更新当前数字的连续序列长度。
- 维护一个变量来记录遍历过程中发现的最长连续序列长度。
代码实现:
class Solution:
def longestConsecutive(self, nums):
if not nums:
return 0
num_set = set(nums)
max_length = 0
for num in num_set:
if num - 1 not in num_set:
# 当前数字是连续序列的开始
current_num = num
current_length = 1
while current_num + 1 in num_set:
current_num += 1
current_length += 1
max_length = max(max_length, current_length)
return max_length
# 示例
solution = Solution()
print(solution.longestConsecutive([100, 4, 200, 1, 3, 2])) # 输出 4
print(solution.longestConsecutive([0, 3, 7, 2, 5, 8, 4, 6, 0, 1])) # 输出 9
这段代码首先定义了一个Solution
类,其中包含了longestConsecutive
方法来解决这个问题。我们使用了一个集合num_set
来存储输入数组中的所有数字,以利用集合的快速查找特性。然后,我们遍历这个集合,对于每个数字,检查它是否是连续序列的开始。如果是,我们就尝试找到这个连续序列的结束,并更新最长连续序列的长度。最后,我们返回找到的最长连续序列的长度。
这段代码是一个算法,用于找出一个未排序的整数数组中最长的连续序列的长度。这里的“连续序列”指的是数字是连续的,但它们在数组中的位置不需要是连续的。
让我们逐步分析代码:
-
类定义:
class Solution:
定义了一个名为Solution
的类,这个类将包含解决问题的方法。
-
方法定义:
def longestConsecutive(self, nums):
定义了一个方法longestConsecutive
,它接收一个数组nums
作为参数。
-
输入检查:
if not nums: return 0
如果数组为空,直接返回0,因为没有连续序列。
-
创建集合:
num_set = set(nums)
将数组nums
转换为一个集合num_set
,这样可以快速检查某个数字是否存在。
-
初始化最长序列长度:
max_length = 0
初始化一个变量max_length
来存储最长连续序列的长度。
-
遍历集合:
for num in num_set:
遍历集合num_set
中的每个数字。
-
检查序列开始:
if num - 1 not in num_set:
检查当前数字num
减1是否不在集合中,如果是,则说明num
可能是一个连续序列的开始。
-
计算连续序列长度:
current_num = num
设置当前检查的数字为num
。current_length = 1
初始化当前连续序列的长度为1。while current_num + 1 in num_set:
使用一个循环,只要current_num + 1
在集合中,就继续增加current_num
的值,同时增加current_length
。
-
更新最长序列长度:
max_length = max(max_length, current_length)
每次循环后,比较并更新max_length
为当前找到的最长序列长度和之前记录的最长序列长度中的较大值。
-
返回结果:
return max_length
在遍历完所有数字后,返回记录的最长连续序列长度。
最后,代码中有两个print
语句用于测试算法:
print(solution.longestConsecutive([100, 4, 200, 1, 3, 2]))
测试用例1,期望输出是4。print(solution.longestConsecutive([0, 3, 7, 2, 5, 8, 4, 6, 0, 1]))
测试用例2,期望输出是9。
这个算法的时间复杂度是O(n),因为它只需要遍历一次集合中的所有数字,并且每次检查和更新操作都是常数时间的。
这段代码的空间复杂度和时间复杂度分析如下:
时间复杂度
- 将数组转换为集合:这一步的时间复杂度是O(n),其中n是数组
nums
的长度。 - 遍历集合中的每个元素:这一步的时间复杂度也是O(n)。
- 在集合中查找元素:对于每个元素,我们最多执行两次查找操作(检查
num - 1
和num + 1
是否存在),查找操作的平均时间复杂度是O(1)。 - 连续序列的增长:对于每个连续序列,我们最多执行n次循环,最坏情况下,这将导致O(n)的时间复杂度。
- 即使考虑
while
循环,总的迭代次数也不会超过数组的长度。
综合考虑,时间复杂度主要由遍历集合和查找操作决定,因此整体时间复杂度是O(n)。
空间复杂度
- 创建集合:集合
num_set
存储了数组nums
的所有元素,因此空间复杂度是O(n)。 - 变量存储:
max_length
和current_length
等变量占用的内存空间相对于n来说是常数级别的。 - 主要由存储集合
num_set
所需的空间决定。
因此,空间复杂度主要由集合num_set
决定,整体空间复杂度是O(n)。
这里的n是数组nums
的长度。这个算法在最坏情况下的时间复杂度和空间复杂度都是线性的,符合题目要求的O(n)时间复杂度。然而,实际性能可能会因为输入数据的特性而有所不同,例如,如果输入数组已经排序或者包含大量重复元素,算法的效率可能会更高。
另外在代码中,while
循环是用来扩展当前连续序列的。在最坏的情况下,如果数组中的数字已经排序,并且形成了一个最长的连续序列,那么每次循环都将检查序列中的下一个数字,直到序列结束。这意味着while
循环可能会执行与连续序列长度相同次数的操作。
假设最长的连续序列包含k
个数字,那么while
循环将执行k-1
次(因为序列的第一个数字在进入循环之前已经被计数了)。因此,对于每个连续序列,我们可能会有O(k)的时间复杂度。
然而,由于我们只对每个数字执行一次这样的while
循环,并且所有连续序列的总长度不会超过n(数组的长度),因此总的时间复杂度仍然是O(n)。这是因为即使有多个连续序列,它们加起来的总长度也不会超过数组的长度。
关于空间复杂度,while
循环不占用额外的空间,因为它只是用来更新计数器和检查集合中的元素,所以空间复杂度仍然是O(n),由存储集合num_set
所需的空间决定。