[LeetCode 128] - 最长连续序列(Longest Consecutive Sequence)

 转载自http://www.cnblogs.com/shawnhue/archive/2013/05/26/leetcode_128.html?utm_source=tuicool&utm_medium=referral

问题

给出一个未排序的整数数组,找出最长的连续元素序列的长度。

如:

给出[100, 4, 200, 1, 3, 2],

最长的连续元素序列是[1, 2, 3, 4]。返回它的长度:4。

你的算法必须有O(n)的时间复杂度

初始思路

要找连续的元素,第一反应一般是先把数组排序。但悲剧的是题目中明确要求了O(n)的时间复杂度,要做一次排序,是不能达到的。不过我们还是先来看看排序的方案要怎么实现。

简单来说,排序后我们只要遍历数组,检查当前值减1是否等于上一个值,如果等,增加当前连续序列的计数;如果不等,将当前计数与当前最大值比较,如果更优替换最大值, 并重置计数为1。具体到细节上,我们还要考虑几个问题:

- 第一个数

处理第一个数时是没有上一个值的。有人可能觉得可以给存储上一个值的变量赋一个特别的初始值来表示处理的是第一个数。但是由于数组内元素的取值范围是所有的整数,不可能找出一个特别的值。所以代码中需要对第一个数做特殊的判断

- 重复的数

数组中可能会有重复的数,所以我们不能光判断当前值减1等于或不等于上一个值。还需要加上一个等不等与上一个值的判断,如果等,说明是一个重复的数字,直接不做任何处理跳到数组中的下一个数。

- 最后一组连续序列

由于我们只在遍历中发现当前值减1不等于上一个值时才尝试更新序列长度最大值。如果有一个连续序列一直持续到数组中的最后一个元素,这个序列的长度是没有得到处理的。因此我们需要在遍历完数组后再尝试更新依稀最大值。

加入了这些细节考虑后,代码就呼之欲出了:

class SolutionSort
{
public:
    int longestConsecutive(std::vector<int> &num)
    {
        std::sort(num.begin(), num.end());
        
        int maxLen = 0;
        int currentLen = 0;
        int previous = 0;
        
        for(auto iter = num.begin(); iter != num.end(); ++iter)
        {
            if(iter == num.begin())//第一个数的特殊情况
            {
                ++currentLen;
            }
            else
            {
                if(*iter == previous)//重复数的情况
                {
                    continue;
                }
                else if(*iter - 1 == previous)
                {
                    ++currentLen;
                }
                else
                {
                    if(currentLen > maxLen)
                    {
                        maxLen = currentLen;
                    }
                    currentLen = 1;
                }
            }
            previous = *iter;
        }
        
                //有一个连续序列持续到数组最后一个元素的情况
        if(currentLen > maxLen)
        {
            maxLen = currentLen;
        }
        
        return maxLen;
    }
};

使用排序的代码

提交后发现其实是可以通过Judge Small和Judge Large的。但是这种方案始终不符合题目要求。

优化

要实现O(n)的时间复杂度,就不能对数组排序。其实我们大脑在判断这个问题时就是不排序的。让我们来模拟一下:

你看到[100, 4, 200, 1, 3, 2]这个数组,首先你会看99或者101在不在这个数组里,发现数组没这两个数,那么100组成的连续序列长度仅为1。接着会看5或者3在不在数组里,会发现3存在,5不存在;紧接着会看2在不在....直到发现0不在。从而得到4组成的最长序列为4。

总结一下会发现,我们在判断某个数的连续序列时,会分别往减小和增大的方向找下一个连续数在不在数组中。然后把两个方向的长度加起来即为包含该数的一个连续序列。需要注意的是,当前数的长度计数只需要出现在一个方向的查找中计算即可,否则就重复了。要找一个数是不是在数组中,不可能用遍历的方法实现,这样时间复杂度就超过O(n)了。而要降低时间复杂度,一个经典的方案就是空间换时间。用增加空间复杂度的方法来换取时间复杂度的降低。所以我们可以先对数组进行一次预处理,生成一份包含数组元素的哈希表。这样在求解某个数字在不在数组时就可以得到O(1)的时间复杂度。

那么我们可以得到如下伪代码:

找连续序列函数(要找序列的值,方向)

  循环直到要找的值不在哈希表中

    序列长度+1

    如果增加方向,要找的序列值+1

    如果减少方向,要找的序列值-1

  循环结束

  返回序列长度

找连续序列函数结束

求解函数(数组)

  遍历数组生成哈希表

  遍历数组

    序列长度1 = 找连续序列函数(当前值,增加方向)

    序列长度2 = 找连续序列函数(当前值 - 1,减少方向)

    如果序列长度1 + 序列长度2 > 当前最长序列,更新最长序列

  遍历结束

求解函数结束

这个方案的时间复杂度应该是O(n) + O(n) * O(1) * O(平均序列长度)。如果平均序列长度等于n,如数组[3,4,2,1],复杂度就是O(n^2)了。看来还不可行,主要的时间复杂度都浪费在找连续序列上了。怎么能减少找连续序列的时间复杂度?经过观察我们可以发现,4的最长序列和3,2,1的最长序列其实是一样的。找过了4之后其实后面这3个数都不用找了。而我们控制是否查找一个数的连续序列是通过判断数字是否在哈希表中来实现的,也就是说,如果我们可以在找出一个数字在连续序列中后就将其移除,就可以避免以后再触发查找的循环。通过这个优化,时间复杂度将变为O(n) + O(n) + O(序列长度总和),可以认为是O(n)了。最后得出代码如下:

classSolution
{
public:
    int longestConsecutive(std::vector<int> &num)
    {
        for(int i = 0; i < num.size(); ++i)
        {
            flags_.insert(num[i]);
        }
            
        int maxLen = 0;
        
        for(int i = 0; i < num.size(); ++i)
        {
            int ascendingMax = FindConsecutiveNumbers(ASCENDING, num[i]);
            int decendingMax = FindConsecutiveNumbers(DECENDING, num[i] - 1);
            
            
            if(ascendingMax + decendingMax > maxLen)
            {
                maxLen = ascendingMax + decendingMax;
            }
        }
        
        return maxLen;
    }
    
private:
    enum OrderBy
    {
        ASCENDING,
        DECENDING
    };
    
    int FindConsecutiveNumbers(OrderBy orderBy, int value)
    {
        int maxLen = 0;
        
        while(flags_.find(value) != flags_.end())
        {
            ++maxLen;
            
            flags_.erase(value);
            
            if(orderBy == ASCENDING)
            {
                ++value;
            }
            else
            {
                --value;
            }        
        }
        
        return maxLen;
    }
    
    std::unordered_set<int> flags_;
};

哈希表的实现

还有一种方法可以避免重复的遍历,加减方向只需要一个方向即可,我们可以在其中查找到起点,然后进行遍历,若不是起点就跳过。





LeetCode(力扣)上,有一个经典的算法题目叫做“最长连续序列”(Longest Continuous Increasing Subsequence),通常用C++或其他编程语言进行解答。这个题目的目的是找到一个给定整数数组中的最长递增子序列。递增子序列是数组中的一段连续元素,它们按顺序严格增大。 这里是一个简单的C++解决方案思路: ```cpp #include <vector> using namespace std; class Solution { public: int longestContinuousIncreasingSubsequence(vector<int>& nums) { if (nums.empty()) return 0; // 避免空数组的情况 int n = nums.size(); vector<int> dp(n, 1); // dp[i] 表示以nums[i]结尾的最长递增子序列长度 int max_len = 1; // 初始化最长递增子序列长度为1 for (int i = 1; i < n; ++i) { // 遍历数组,从第二个元素开始 if (nums[i] > nums[i-1]) { // 如果当前元素比前一个大 dp[i] = dp[i-1] + 1; // 更新dp值,考虑加入当前元素后的增长长度 max_len = max(max_len, dp[i]); // 检查是否更新了最长序列长度 } } return max_len; // 返回最长连续递增子序列的长度 } }; ``` 在这个代码中,我们使用了一个动态规划(Dynamic Programming)的方法,维护了一个数组`dp`来存储每个位置以该位置元素结尾的最大递增子序列长度。遍历过程中,如果遇到当前元素大于前一个元素,则说明可以形成一个新的递增子序列,所以将`dp[i]`设置为`dp[i-1]+1`,并更新全局的最长序列长度。 如果你想要深入了解这个问题,可以问: 1. 这个问题的时间复杂度是多少? 2. 动态规划是如何帮助解决这个问题的? 3. 如何优化这个算法,使其空间复杂度更低?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值