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
HashSet
HashSet
是 Java 集合框架中的一个重要成员,它实现了 Set
接口,是一个无序的、不允许存储重复元素的集合。
定义
HashSet
是基于哈希表(实际上是一个 HashMap
实例)实现的 Set
接口的实现类,它继承自 AbstractSet
类并实现了 Set
、Cloneable
和 Serializable
接口。其定义形式如下:
import java.util.HashSet;
HashSet<E> set = new HashSet<>(); // E 表示集合中元素的类型
特点
- 不允许重复元素:这是
HashSet
最显著的特点之一。当向HashSet
中添加元素时,它会使用元素的哈希码(hashCode
)和equals
方法来判断元素是否已经存在。如果两个元素的哈希码相同且equals
方法返回true
,则认为这两个元素是重复的,不会将重复元素添加到集合中。 - 无序性:
HashSet
中的元素没有固定的顺序,不能保证元素的插入顺序和取出顺序一致。这是因为它是基于哈希表实现的,元素在集合中的存储位置是根据其哈希码计算得出的。 - 允许存储
null
元素:HashSet
允许存储一个null
元素。由于null
没有哈希码,HashSet
对其做了特殊处理,只允许存储一个null
。
实现原理
HashSet
内部是通过 HashMap
来实现的。当向 HashSet
中添加元素时,实际上是将元素作为 HashMap
的键来存储,而 HashMap
的值则是一个固定的对象(PRESENT
,是 HashSet
内部定义的一个静态常量)。在判断元素是否重复时,先通过元素的 hashCode
方法计算哈希值,确定其在哈希表中的位置,然后再通过 equals
方法比较元素内容是否相同。
常见方法
add(E e)
:向集合中添加一个元素,如果元素已存在则不添加,并返回false
;否则返回true
。remove(Object o)
:从集合中移除指定的元素,如果集合中包含该元素则返回true
;否则返回false
。contains(Object o)
:判断集合中是否包含指定的元素,如果包含则返回true
;否则返回false
。size()
:返回集合中元素的数量。isEmpty()
:判断集合是否为空,如果为空则返回true
;否则返回false
。
解题思路
使用哈希集合(HashSet)来快速查找元素是否存在。可以将数组中的所有元素存入HashSet中,然后对于每个可能的起点,比如某个数的前一个数不在集合中,那么这个数可能是序列的起点,然后从这个数开始不断检查+1的数是否存在,统计长度。这样,每个数最多被访问两次(一次作为起点,一次在其他数的序列中被检查),时间复杂度是O(n)。
比如,示例1中的数组 nums=[100,4,200,1,3,2] 存入HashSet后,遍历每个元素。比如当前数是100,检查99是否存在,不存在,所以以100为起点,向后找101是否存在,不存在,所以序列长度是1。接着是4,检查3是否存在,存在的话,说明4可能不是起点,跳过。对于200同理。而遇到1时,检查0不存在,所以作为起点,找2是否存在,存在,3也存在,4也存在,直到找不到下一个,此时序列长度是4。这样就能找到最长的长度。
具体步骤
1.将所有元素存入HashSet,以便O(1)时间查找是否存在。(创建 HashSet,将所有nums元素存入)
2.遍历集合中的每个元素,对于每个元素num,检查num - 1是否存在于集合中。如果不存在,说明num可能是一个连续序列的起点。
3.如果是起点,则开始循环检查num+1,num+2...是否存在,直到找不到下一个数为止,统计当前序列的长度。
4.记录最大的序列长度。
接下来需要考虑如何实现这个逻辑。例如,如何避免重复处理元素。比如,当某个元素已经被包含在之前的某个序列中时,不需要再处理它,因为作为非起点的元素,其所在的序列已经被处理过了。比如,当处理元素4的时候,发现3存在,那么4所在的序列起点应该是3或者更小,所以当处理到3的时候才会作为起点处理,此时4会被包括进去。所以,对于非起点的元素,可以跳过处理。
代码实现
具体代码步骤:
1. 创建HashSet,并添加所有元素。
2. 初始化maxStreak为0。
3. 遍历数组中的每个数num:
a. 如果集合中不包含num-1,则开始检查连续序列。
b. 当前数为currentNum = num,currentStreak =1。
c. 循环:当集合中包含currentNum+1时,currentNum+1,currentStreak+1。
d. 比较currentStreak和maxStreak,取较大的。
4. 返回maxStreak。
class Solution {
public int longestConsecutive(int[] nums) {
Set<Integer> numSet = new HashSet<>();
for(int i = 0; i < nums.length; i++){
numSet.add(nums[i]);
}
int maxStreak = 0;
//遍历集合而非原数组,避免重复元素多次判断
for(int num : numSet){
if(!numSet.contains(num - 1)){ //仅当当前数是连续序列的起点时处理
int currentNum = num;
int currentStreak = 1;
while(numSet.contains(currentNum + 1)){
currentNum++;
currentStreak++;
}
maxStreak = Math.max(maxStreak,currentStreak);
}
}
return maxStreak;
}
}