1. 程式人生 > >LeetCode:128. Longest Consecutive Sequence

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。