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。