LeetCode:128. Longest Consecutive Sequence

在看这篇文章前,你也许想要先看看并查集是如何实现的:https://blog.csdn.net/weixin_43462819/article/details/83626022
这一题是实现完并查集之后练手的第二题,可以先看看第一题:
https://blog.csdn.net/weixin_43462819/article/details/83628052

题目是这样的:

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

Your algorithm should run in O(n) complexity.
Example:
Input: [100, 4,200, 1, 3, 2]
Output: 4
Explanation: The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4.

这题的第一反应肯定是先排序,但是题目要求时间复杂度为O(n),所以排序的方法不行。
下面考虑用并查集的方法来做。我们创建一个map,键为输入的数组的每个值,值为该键对应的数组索引。每次迭代一个数组元素,如果在map中有键值为该元素减一或者加一,那么说明它们是相连的,需要对它们的值做Union操作。
另外,我们对WeightedQuickUnionUF稍作改动,增加了一个成员函数:MaxUnion(),用来返回最大分量所含元素的个数。
下面是代码:

class WeightedQuickUnionUF {
private:
    std::vector<size_t> id;
    size_t count;
    std::vector<size_t> sz;
public: 
    WeightedQuickUnionUF(size_t n):count(n) {
        id.reserve(n);//improve the performance
        for (size_t i = 0; i < n; ++i)
            id.push_back(i);
        sz.reserve(n);
        for (size_t i = 0; i < n; ++i)
            sz.push_back(1);
    }


    size_t Count() const {
        return count;
    }

    bool Connected(size_t p, size_t q) const {
        return Find(p) == Find(q);
    }

    size_t Find(size_t p) const {
        Validate(p);
        while (p != id[p])
            p = id[p];
        return p;
    }

    void Union(size_t p, size_t q) {
        Validate(p);
        Validate(q);

        auto pRoot = Find(p);
        auto qRoot = Find(q);

        if (pRoot == qRoot) return;

        if (sz[pRoot] < sz[qRoot]) {
            id[pRoot] = qRoot;
            sz[pRoot] += sz[qRoot];
        }
        else {
            id[qRoot] = pRoot;
            sz[pRoot] += sz[qRoot];
        }
        --count;
    }
    
    size_t MaxUnion() {
        vector<size_t> v(id.size(), 0);
        for (int i = 0; i < id.size(); ++i)
            ++v[Find(i)];
        return *max_element(v.cbegin(), v.cend());
    }

private: 
    void Validate(size_t p) const {
        if (p >= id.size())
            throw std::out_of_range("index out of range");
    }
};


class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if (nums.empty())
            return 0;
        WeightedQuickUnionUF uf(nums.size());
        map<int, int> m;
        for (int i = 0; i < nums.size(); ++i) {
            if (m.find(nums[i]) != m.end())
                continue;
            m[nums[i]] = i;
            if (m.find(nums[i]-1) != m.end()) 
                uf.Union(i, m[nums[i]-1]);
            if (m.find(nums[i]+1) != m.end())
                uf.Union(i, m[nums[i]+1]);
        }
        return uf.MaxUnion();
    }
};

注意其中的一个细节:

if (m.find(nums[i]) != m.end())
    continue;

也就是当我们遇到数组里面的重复元素时,会直接略过。我们探讨一下这会带来什么影响:假设在nums的位置0和位置5都是数字3。那么当我们循环到位置5的时候,我们是直接略过。那么uf中的id对应的数字id[5]仍然还是初始化的5,肯定会和位置0的id[0]不同,也就是uf.Find(0) != uf.Find(5)。这就违反了我们的直觉,数字一样但它们的根不一样?是的,但是对于这题这样做没有错。因为这题要求的是分量的元素最多为多少,不对id[5]作改动的影响是我们会多出一个分量,但对于0所在分量的个数计算没有影响,所以对于该题是不错的。但是如果把题目要求改一下,比如要求有多少组连续的序列,也就是要求分量有多少个,那么这种做法就是错的了,需要修改。

ok,有关并查集的题目也练手了两题了,我觉得做相关的题目,也就是“应用”并查集,而不是“实现”并查集的关键,*就是将如何将原题目所提供的信息转化为合适的输入。*比如前一题:https://blog.csdn.net/weixin_43462819/article/details/83628052 ,如果两个岛在输入的二维表中是上下左右的关系,那么它们就是相连的,对它们进行union;对于这一题,如果有数字比它大一或者小一,那么它们就是相连的,对它们进行union。

猜你喜欢

转载自blog.csdn.net/weixin_43462819/article/details/83651904