在看这篇文章前,你也许想要先看看并查集是如何实现的: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。