LeetCode 128. Longest Consecutive Sequence

题目描述:

Given an unsorted array of integers, find the length of the longest consecutive elements sequence.

For example,
Given [100, 4, 200, 1, 3, 2],
The longest consecutive elements sequence is [1, 2, 3, 4]. Return its length: 4.

Your algorithm should run in O(n) complexity.

解题思路:
拿到这个问题首先想到的是对这组数据排个序, 然后找排序数组中最长的连续数组就好了, 然而这种方法涉及排序的话, 时间复杂度一定是 O(nlgn) 的, 和题目中要求的 O(n) 是有一定差距的。那么想要降低时间复杂度, 就需要从用空间换时间出发来考虑这个问题了, 想到这里, 这道题目可以说走完一半了。
用hash表来解决这个问题,先初始化一个hash表, 存储所有数组元素, 然后遍历这个数组, 对找到的数组元素, 去搜索其相连的上下两个元素是否在hash表中, 如果在, 删除相应元素并增加此次查找的数据长度, 如果不在, 从下一个元素出发查找。

代码:

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> myset;
        // 将所有数据放入一个hash 表
        for_each(nums.begin(), nums.end(), [&](int a){myset.insert(a); });

        // 遍历数组, 查询hash 表中是否存在该值 或者相邻的数值, 若无, 说明没有, 如有, 删除并将计数长度加1
        int maxLen = 0;
        for (int i = 0; i != nums.size(); i++){
            int len = 0;
            if (myset.find(nums[i]) != myset.end()){
                len++;
                myset.erase(nums[i]);

                // 向下继续搜索连续数据
                for (int ii = nums[i] - 1;; ii--){
                    if (myset.find(ii) != myset.end()){
                        len++;
                        myset.erase(ii);
                    }
                    else
                        break;
                }

                // 向上继续搜索连续数据
                for (int ii = nums[i] + 1;; ii++){
                    if (myset.find(ii) != myset.end()){
                        len++;
                        myset.erase(ii);
                    }
                    else
                        break;
                }
            }

            // 更新最大长度
            if (maxLen < len)
                maxLen = len;
        }

        return maxLen;
    }
};

网上大神的解法:
demo1:
思路是一致的, 不过代码精简了许多:

class Solution {
public:
    int longestConsecutive(vector<int> &num) {
        unordered_set<int> record(num.begin(),num.end());
        int res = 1;
        for(int n : num){
            if(record.find(n)==record.end()) continue;
            record.erase(n);
            int prev = n-1,next = n+1;
            while(record.find(prev)!=record.end()) record.erase(prev--);
            while(record.find(next)!=record.end()) record.erase(next++);
            res = max(res,next-prev-1);
        }
        return res;
    }
};

demo2:
非常精简, demo 2 的思路和 demo 3 是一样的, 不过demo 3 注释比较多, 看起来稍微省力些。

/*
use a hash map to store boundary information of consecutive sequence for each element; there 4 cases when a new element i reached:

1) neither i+1 nor i-1 has been seen: m[i]=1;

2) both i+1 and i-1 have been seen: extend m[i+m[i+1]] and m[i-m[i-1]] to each other;

3) only i+1 has been seen: extend m[i+m[i+1]] and m[i] to each other;

4) only i-1 has been seen: extend m[i-m[i-1]] and m[i] to each other.
*/
int longestConsecutive(vector<int> &num) {
    unordered_map<int, int> m;
    int r = 0;
    for (int i : num) {
        if (m[i]) continue;
        r = max(r, m[i] = m[i + m[i + 1]] = m[i - m[i - 1]] = m[i + 1] + m[i - 1] + 1);
    }
    return r;
}

demo 3
需要特别注意:
这里 n + m[n + 1] 可以理解为是 找到 他的右边界, m[n + 1] 表示 n 右边相连的一段连续数据的长度, 于是 n + m[n + 1] 就是 右边界了

int longestConsecutive(vector<int> num) {
    unordered_map<int,int> m;
    int ret = 0;
    for(auto & n: num){

        //it is in the middle of some consecutive sequence OR we can say it is already visited earlier
        //therefore it does not contribute to a longer sequence
        if(m[n]) continue; 

        //we cannot find adjacent sequences to n, therefore it is a single element sequence by itself
        if(m.find(n-1) == m.end() && m.find(n+1) == m.end()){ // 
            ret = max(ret, m[n] = 1);
            continue;
        }

        //found a sequence at n+1
        //you may wonder what if the sequence at n+1 contains element n?
        //It it contains n, when we add the length by 1 using m[n+1]+1, it is wrong, right?
        //However it is not possible, because if sequence at n+1 contains n, m[n] must have been visited earlier
        //we checked that using if(m[n]) continue; here m[n] is not yet visited;
        //therefore sequence m[n+1] is always on right side, we can safely extend the length by 1
        if(m.find(n-1)==m.end()){ 

            //we want to maintain the TWO boundaries of the sequence
            //the new length of the sequence is the original length m[n+1] incremented by 1
            //left boundary m[n] = m[n+1] +1;
            //right boundary m[n+m[n+1]] = m[n+1]+1;
            //why n+m[n+1]? it is equal to m[n+1]+(n+1)-1 
            //meaning the old left boundary n+1 plus the old length m[n+1] minus 1
            //e.g. for sequence 3,4,5,6 m[3] = 4, and right boundary 6 = 3+m[3]-1 (here n+1 == 3);
            //
            // 这里 n + m[n + 1] 可以理解为是 找到 他的右边界, m[n + 1] 表示 n 右边相连的一段连续数据的长度, 于是 n + m[n + 1] 就是 右边界了
            //
            int r = m[n] = m[n+m[n+1]] = m[n+1]+1;
            ret = max(ret, r);
            continue;
        }

        //this is similar to the above case just extend to the right
        if(m.find(n+1)==m.end()){
            int r = m[n] = m[n-m[n-1]] = m[n-1]+1;
            ret = max(ret,r);
            continue;
        }

        //here, we found both sequences at n+1 and n-1, for reasons we explained,
        //the sequences have no overlap.
        //Now, we just need to add the length of current element n (which is 1) to both left and right boundaries
        //the new length will be :  
        //old length of left sequence (m[n-1]) + old length of right sequence (m[n+1]) + 1
        //We also need to mark m[n] as visited, here we can either mark it with 1 or the new length;
        int r = m[n-m[n-1]] = m[n+m[n+1]] = 1+ m[n+1]+ m[n-1];
        m[n] = 1; //basically we just need to mark m[n] as any non-zero number
        // or we can write
        //int r = m[n] = m[n-m[n-1]] = m[n+m[n+1]] = 1+ m[n+1]+ m[n-1];
        ret = max(ret,r);
    }
    return ret;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值