问题描述:给定一个无序的序列,输出最长连续子序列的长度。
比如:
给定[100, 4, 200, 1, 3, 2]
最长的连续子序列是[1, 2, 3, 4],其长度为 4.
问题要求:算法时间复杂度为O(n)。
思路:最近在研究DP,首先想到了LIC,但这个问题的时间复杂度要求很高,LIC优化的也要O(NlogN),再想,题目给定的是无序的,简单情况如果是有序的是不是很好处理呢?当然很好处理,以升序为例,只要比较相邻两个元素的差值是否为1,然后长度相应增加1,在条件不满足时看当前获得的长度是否比最大长度大,则可。
但再想想我们掌握的排序算法,除了用我们眼睛去瞄,没有稳定的能在O(N)时间里完成的算法,排序这条路走不通。
别灰心,想想<<让子弹飞>>里县长夫人说过的一句话“有什么就说什么嘛”,要是我们要什么就有什么那么这个问题也可以在规定的时间复杂度下完成,我们要什么呢?比如我们看见100,我想要99,能一下就给我答案告诉我有没有吗?别告诉我要用遍历,咱想站着把钱赚了,所以这个要在O(1)的时间完成,同样我要101能在O(1)告诉我吗?当然可以,谁让你是张麻子呢,hash可以完成。
接下来我们具体说说用hash类的东西怎么来处理这个问题。
1)给定的序列里可能,可以说肯定有重复的元素,但这些重复的元素对问题本身没影响,就是说上例中有1个4和n个4
结果是一样的,但会让我们的操作繁琐。
2)上例中,在处理4的时候,我们是去找有3没有,有2没有,有1没有、、、有5没有,有6没有、、、。在处理1的时候还会去找有2没有,有3没有,有0没有。其实这就产生了不必要的重复,通过4找到的结果是[1,2,3,4],通过2,3,1找到的结果同样是[1,2,3,4]。
对于问题1的重复问题我们可以用set来存储给定的序列,这样就去除了重复。
对于问题2的重复问题我们可以在处理完将其删除,这样就不会再次处理。
综合以上的分析,我们确定,要用unordered_set(hash_set)来重新存储给定序列。
分析完就是些程序来实现,其实这个问题实现起来不复杂,就直接上代码。
//代码:
#define _CODE_LEETERCODE_LONGESTCONSECTIVESEQUENCE_ #ifdef _CODE_LEETERCODE_LONGESTCONSECTIVESEQUENCE_ #include <vector> #include <iostream> #include <unordered_set>//hash_set using namespace std; /* Description: Given an unsorted array of integers, find the length of the longest consecutive elements sequence. for example, Give [100, 4, 200, 1, 3, 2] the lcs is [1,2,3,4], return 4. Algorithm: O(n). */ class Solution{ public: int longestConsecutive(vector<int> &num) { if(num.size() == 0 || num.size() == 1) return num.size(); int max_len = 0; unordered_set<int> iset; vector<int>::iterator iter = num.begin();
//copy to unordered_set iset. while (iter != num.end()) { iset.insert(*iter); ++iter; } unordered_set<int>::iterator siter; for (iter = num.begin(); !iset.empty() && iter != num.end(); ++iter) { int cur_len = 0; siter = iset.find(*iter); int ifind = *iter; while(siter != iset.end())//search its left item. { cur_len++; iset.erase(siter); siter = iset.find(--ifind); } ifind = *iter + 1; siter = iset.find(ifind); while(siter != iset.end())//search its right item. { cur_len++; iset.erase(siter); siter = iset.find(++ifind); } if(cur_len > max_len) max_len = cur_len; } return max_len; } }; int main() { int n[] = {100, 1, 200, 3, 2, 4}; vector<int> ivec(n,n+6); Solution s; return s.longestConsecutive(ivec); } #endif
1)程序中的for循环中,循环条件加上!iset.empty(),判断iset是否为空我们可以省略很多不必要的操作,因为iset中的元素在开始的时候就比给定序列少(没重复),而且随着操作我们对iset是不停的在进行删除操作,所以会越来越少。
2)对于每个元素,我们总是向该元素的两端去寻找,这样才能找到其构成的最长的连续序列。
总结:
这个问题其实就是公差为1的等差数列的最大长度,以此可以演化出许多其他问题,比如公差为d(d不仅限于1)的等差数列,或者是公比为q的等比数列等。这个问题可以说是动态规划经典问题LIC的特殊形式,再此不表,有兴趣的可以尝试下这些情形。