题目
给定一个未排序的整数数组,找出最长连续序列的长度。
要求算法的时间复杂度为 O(n)。
示例:
|
|
题解
这道题要求实现复杂度为O(N),因此一开始也就往这个复杂度去想,既然是O(N),那么肯定遍历一次就够,如果想得知连续这个状态,必须要将一些中间数据存储下来。因为数据的大小未知,所以不能通过数组记录中间状态,要通过map。
后来仔细想了下,有了大致的思路,但实现起来的复杂度并不是O(N),所以很纠结。以[100, 4, 200, 1, 3, 2]
为例,思路大致如下:
定义map为dp,遍历整个数组,首先我们得到100,这样dp[100]等于dp[99] + 1 + dp[101],即dp[i] = dp[i - 1] + 1 + dp[i + 1]。由于dp[99]和dp[101]都不存在,默认为0,所以dp[100]为1。
接下来得到4,同样dp[4] = 1 + dp[3] + dp[5],dp[4]还等于1。之后是200,结果依旧为1。接下来是1,结果依旧为1。再然后是3,这个时候就得注意了,dp[3] = 1 + dp[2] + dp[4],其中dp[4]为1,那么dp[3]为2。再之后是2,dp[2] = 1 + dp[1] + dp[3],便是1 + 1 + 2 = 4。
具体的内存图如下:
1 | 2 | 3 | 4 | 100 | 200 |
---|---|---|---|---|---|
1 | 4 | 2 | 1 | 1 | 1 |
虽然上述算法得出了结果,但存在一个问题,如果数组最后还有一个5,那么dp[5] = dp[4] + 1 + dp[6],这样计算出的结果为2,显然是不对的。因此我们在上述计算过程中需要将结果蔓延至边界。也即当我们计算出dp[2]的结果时,我们要往两边蔓延,将dp[1]的结果改为4,将dp[4]的结果也改为4。这样再来一个5时就不会有问题了。
但加了这个蔓延操作后,显然复杂度就不是O(N)了,下面是自己的实现,提交后果然超时。
|
|
以上提交超时,看了下相关的讨论,果然是有强人的。我在实现蔓延操作时,使用的是遍历,但实际上不需要这样,两边结果中存储的数就是我们要蔓延的目的地。当计算完dp[2]之后,我们要进行蔓延操作,这个时候看dp[3] = 2,那么dp[2 + 2]既是我们要修改的边界。dp[1] = 1,那么dp[2 - 1]既是我们要修改的边界。
我们在进行上述计算时,实际上是已经把当前值到边界的值存储了两边的dp结果中了。因此不用像我那样进行循环查找边界,实现了O(N)的时间复杂度,对应的实现如下:
|
|
另外,从讨论中还看到了一种基于hash表的实现,实现如下:
|
|
大致思想是,首先对所有的数字去重,之后遍历去重后的数字,通过hash表找到连续的最小数字,之后由这个最小的数字往后循环,找到连续的最大数字。