给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-consecutive-sequence
灵感来源:
作者:yimeixiaobai
链接:https://leetcode.cn/problems/longest-consecutive-sequence/solution/xiao-bai-lang-ha-xi-ji-he-ha-xi-biao-don-j5a2/
看到本题找最长连续序列,每个人都会想到两种最朴素的解法之一:
1.先排序,从前往后找最长连续上升序列即可。该思路简单有效,但是复杂度已经至少有O(nlogn)O(nlogn)。实现起来也比较简单,在此不讨论该解法。
2.遍历数组中的每个元素num,然后以num为起点,每次+1向后遍历num+1,num+2,num+3...,判断这些元素是否存在于数组中。假设找到的最大的连续存在的元素为num+x,那么这个连续序列的长度即为x+1。最后将每个num所开始序列长度取个最大值即可。
这个思路很通俗易懂,实现成代码,如下所示:
class Solution {
public int longestConsecutive(int[] nums) {
int n = nums.length;
int ans = 0;
// 遍历数组中的每个元素num
for (int i = 0; i < n; i++) {
// 以num为起点,每次+1向后遍历num+1,num+2,num+3...
int cur = nums[i] + 1;
while (true) {
// 标识是否在数组中找到了cur
boolean flag = false;
// 在数组中找cur
for (int j = 0; j < n; j++) {
if (nums[j] == cur) {
flag = true;
break;
}
}
if (!flag) {
break;
}
cur += 1;
}
ans = Math.max(ans, cur - nums[i]);
}
return ans;
}
}
上面的代码是超时的,我们将它优化成不超时的。
(1)用HashSet代替直接遍历,可以降低复杂度,而且可以去重复。
(2)上面的方法的缺点:比如元素[1,2,4,3,5],上面的方法是:先选择1,遍历数组查找数组中有没有1+1=2,有的话再查找有没有2+1=3,以此查找到了5,然后再选择2,查找数组中有没有3,以此查找到5,这里就能看出出现了重复的代码,在选择1时,我们查找了2,3,4,5,,而选择2时,我们又查找了3,4,5,这样就重复了。
我们的改进:遍历数组中每个元素num。逐一遍历每个元素会产生很多冗余工作,实际上我们无需一次针对每个元素num去判断num+1,num+2,num+3...是否在数组中。如果num-1已经在数组中的话,那么num-1肯定会进行相应的+1遍历,然后遍历到num,而且从num-1开始的+1遍历必定比从num开始的+1遍历得到的序列长度更长。因此,我们便可将在一个连续序列中的元素进行删减,让其只在最小的元素才开始+1遍历,也就是:只有当num-1不存在时,才开始向后遍历查找num+1,num+2,num+3
比如,现有元素[1,2,4,3,5],当2,3,4,5发现均有比自己小1的元素存在,那么它们就不会开始+1遍历,而1是连续序列中最小的元素,没有比自己小1的元素存在,所以会开始+1遍历。通过上述方式便可将时间复杂度优化至O(n)。
class Solution {
public int longestConsecutive(int[] nums) {
// 建立一个存储所有数的哈希表,同时起到去重功能
Set set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int ans = 0;
// 遍历去重后的所有数字
for (int num : set) {
int cur = num;
// 只有当num-1不存在时,才开始向后遍历num+1,num+2,num+3......
if (!set.contains(cur - 1)) {
//查看数组中是否有 cur+1 ,有的话就继续查找 cur +1 +1
while (set.contains(cur + 1)) {
cur++;
}
}
// [num, cur]之间是连续的,数字有cur - num + 1个
ans = Math.max(ans, cur - num + 1);
}
return ans;
}
}
这个方法就是对上面超时方法的一些改进,好理解
感悟:当我们拿到一个题时,没有头绪时,不妨先写写你知道这个方法会超时的方法,然后思考如何将其改进,而不是一上来就想难的方法。