给定一个未排序的整数数组 nums
,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n)
的算法解决此问题。
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> numSet = new HashSet<>();
// 添加所有元素到HashSet中
for (int num : nums) {
numSet.add(num);
}
int longestStreak = 0;
for (int num : nums) {
// 只有当num-1不在集合中时,才考虑num作为新的序列起点
if (!numSet.contains(num - 1)) {
int currentNum = num;
int currentStreak = 1;
// 不断尝试寻找序列中的下一个数
while (numSet.contains(currentNum + 1)) {
currentNum += 1;
currentStreak += 1;
}
// 更新最长序列的长度
longestStreak = Math.max(longestStreak, currentStreak);
}
}
return longestStreak;
}
}
解题思路
首先这一题要求时间复杂度O(n),因此不能自己手动进行排序(时间复杂度已经O(n*logn)了,我们可以使用HashSet
来实现。主要思路是使用哈希表存储所有的数字,然后遍历数组,对于每个元素尝试扩展其连续序列的范围,并在此过程中计算最长序列的长度。
HashSet的介绍
将所有元素添加到 HashSet
中是解决这个问题的关键步骤,这种方法有几个重要的优点:
-
去重:如果数组中包含重复的元素,
HashSet
会自动去除这些重复项。这样做可以避免在寻找连续序列时对同一个数字重复计算。 -
快速查找:
HashSet
的查找时间复杂度为 O(1),这意味着无论集合中有多少元素,查找操作的速度都非常快。在这个问题中,我们需要频繁地检查某个元素是否存在于集合中,特别是在判断连续序列时,能否快速判断一个元素是否存在直接影响算法的整体效率。 -
确定序列起点:算法的核心在于找到连续序列的起点,然后从这些起点开始向后寻找连续的元素。使用
HashSet
可以快速确定一个数字是否可以作为序列的起点,即检查num-1
是否不存在于集合中。这样做可以有效地减少不必要的搜索,因为如果num-1
存在,那么num
显然不是序列的起点,这个序列应该从更早的数字开始。
if嵌套while的逻辑
这段代码是解决“最长连续序列”的问题,其目的是在一个未排序的整数数组中找到最长的连续元素序列的长度。为了有效地解决这个问题,它采用了一种特别的策略:首先通过 HashSet
去重并实现 O(1) 的查找效率,然后只从可能是连续序列起点的数字开始探索。
为什么用 if
检查 num-1
当检查到某个数字 num
时,使用 if (!numSet.contains(num - 1))
是为了确认 num
是否可能是某个连续序列的最小元素(即序列的起点)。如果 num-1
存在于集合中,这意味着 num
不是起点,因为它至少有一个前驱元素。我们只从序列的起点开始计算,以避免重复和无效的计算。
为什么在 if
内部使用 while
循环
一旦确认了 num
是连续序列的起点,接下来就需要计算这个序列的长度。这里使用 while
循环不断地检查 numSet
中是否存在 currentNum + 1
,每找到一个就将 currentNum
自增并更新当前序列的长度 currentStreak
。这个过程会一直进行,直到找不到下一个连续的数字为止。因此,while
循环是为了从当前的序列起点开始,逐个数地向后寻找连续的数字,直到序列结束。
逻辑是怎样的
这里的逻辑是对的,而且非常高效。通过首先使用 if
来定位连续序列的起点,然后用 while
循环去扩展这个序列,直到找不到更多连续的元素,我们可以准确地计算出每一个连续序列的长度,并最终找到最长的那一个。
总结
if
判断:确定一个数是否是连续序列的起点。while
循环:从这个起点开始,向后寻找连续的序列,并计算这个序列的长度。
这种方法的优点是避免了不必要的计算,因为它只计算每个连续序列一次,且只从每个序列的实际起点开始计算,极大地提高了效率。希望这个解释帮助你理解了这段代码的逻辑和有效性。