题目描述
给定一个无序整型数组,请找出最长的连续整数序列。
要求使用 $O(n)$ 时间复杂度的算法。
样例
输入:
[100, 4, 200, 1, 3, 2]
输出:
4
解释:最长的连续整数序列是 [1, 2, 3, 4]
,其长度是4。
算法
(哈希) $O(n)$
从前往后扫描整个数组,过程中用两个哈希表unordered_map<int,int>tr_left, tr_right
维护所有连续整数序列,两个哈希表分别将序列的左右端点映射成序列长度。
然后我们考虑如何维护哈希表:
当我们遍历到一个新的数 $x$ 时,先查找 $x$ 左右两边分别存在多长的连续序列,两个值分别是tr_right[x-1]
和tr_left[x+1]
,分别记为left
和right
,此时我们可以将左右两部分和 $x$ 拼起来,形成一个更长的连续整数序列,然后更新新序列的左右两端的值:
- 新序列的左端点是
x-left
,更新哈希表:tr_left[x - left] = max(tr_left[x - left], left + 1 + right);
- 新序列的右端点是
x+right
,更新哈希表:tr_right[x + right] = max(tr_right[x + right], left + 1 + right);
最后我们不要忘记用新序列的长度left+right+1
更新答案。
时间复杂度分析:对于每个数,仅被遍历一次,且遍历时只涉及常数次哈希表的增改查操作,所以总时间复杂度是 $O(n)$。
C++ 代码
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
int res = 0;
unordered_map<int, int> tr_left, tr_right;
for (auto & x : nums)
{
int left = tr_right[x - 1];
int right = tr_left[x + 1];
tr_left[x - left] = max(tr_left[x - left], left + 1 + right);
tr_right[x + right] = max(tr_right[x + right], left + 1 + right);
res = max(res, left + 1 + right);
}
return res;
}
};
可以只用一个哈希表记录每个数字所能构成的最大长度
当扫描到一个新数字时,找该数字左右数字的最大长度,合并后,更新该数字和新连续序列左右数字的哈希值。
如果当前扫描到的数已经在哈希表中了,则直接跳过。
有道理!哈希表中记录每个数为边界的最大长度,则只有区间边界的数有意义,如果遇到内部的数,我们直接跳过。当遍历到 $x$ 时,先求出 $x-1$ 的最大长度,则由于 $x$ 第一次遍历到,所以 $x-1$ 如果存在,则一定是某个区间的右端点,同理 $x+1$ 一定是某个区间的左端点。所以我们同时开两个哈希表,记录左右两边的哈希值是多余的,仅需一个哈希表即可。
赞
“tr_left[x - left] = max(tr_left[x - left], left + 1 + right);”为什么要取max呢,left+1+right不就应该是当前以x-left为左端点的序列长度最大值吗
所有极大区间两端的点存储的tr_left和tr_right都是整个区间的长度,但区间内的点存储的长度可能比实际值要小。
可以试试这个样例,正确答案是5,如果去掉max操作会得到4:
应该说取最大值是为了处理重复数字吧?
即使数字没有重复,也是有这个问题的。实际上是因为我们没有删除冗余的小区间,如果合并区间的同时把冗余小区间删除,就没有这个问题了。
如果仍有问题,可以在代码中把每一步所维护的所有区间打印出来看一下。
yxc 太神啦~~
// 现在感觉这个方法最简单
// O(n),每个点要么作为起点,要么作为扩展点
y神,这道题用并查集怎么做呢
那种做法本质上是单链表,只是代码有点像并查集。一开始将所有点x的父节点初始化成x + 1,然后按任意顺序遍历所有点,对于每个点,如果其父节点存在,则走到父节点,直到不能走为止,那么最后走到的点就是整段的最大值,然后回溯的时候将所有点的父节点改成刚刚找到的根节点即可。
很棒!
感觉很难想出来这种做法,是怎样联想到的,或者哪个例题衍生的
多做题就好了,量变引起质变
讨论里的
棒!
哈哈 怎么感觉一个哈希有点dp的味道!
对滴,有点像