题目描述
知识点
并查集
结果
不太好,排名好低呀
实现
码前思考
- 要不是实现知道这个题得用并查集,我可能根本想不到要用并查集;
- 时间复杂度为
这就很恐怖,不能使用动态规划里的
LIS
了; - 考虑到数字重复的情况,通过题意可以知道,数字重复和一个数字,它们的价值是等价的,也就是说重复数字可以看作是一个数字!!!
- 由于题中没有给出数字的大小,可能是正的,也可能是负的,我们得
hash
到可以到可用的非负数范围之内,但是由于我不太会hash
,所以失败了。最后思考发现可以使用unordered_map
来实现,这种数据结构采用哈希表实现,时间复杂度为 ,而map
使用红黑树,时间复杂度为 ; - 我这里的并查集father的意思是:以该节点为起始的最长序列包含的元素。 但是是不包含该节点的。这个思路有点绕弯,后面的码后反思的那个思路更好一点;
- 由于我们是每次遍历读取
nums
中的数据cur
将cur
合并到前一个数字cur-1
上,所以可能前一个数字cur-1
不存在于nums
中,所以还得另外把每个数字的前一个数字cur-1
加到unordered_map
中,这样确实复杂多了,唉。
代码实现
//时间复杂度为O(n)这就很恐怖,不能使用动态规划了
class Solution {
private:
//father数组,初始化为自己
//使用unordered map来存储
unordered_map<int,int> father;
//每棵树的结点的个数,表示从该结点开始的最小序列的大小
unordered_map<int,int> count;
//最大连通分量的大小
int ans;
public:
int longestConsecutive(vector<int>& nums) {
if(nums.size() == 0){
return 0;
}
//初始化两个数组
//初始化father
for(auto i : nums){
father[i] = i;
father[i-1] = i-1;
count[i] = 1;
count[i-1] = 1;
}
ans = 1;
for(auto i : nums){
//对cur进行映射,cur应该是从1开始的
int cur = i;
int pre = i-1;
int fatherCur = findFather(cur);
int fatherPre = findFather(pre);
if(fatherCur == fatherPre){//说明已经在一个并查集里面了,不需要进行操作
continue;
}else{ //如果不在一个并查集里面,需要进行合并
//将大的数合并到小的数的集里面
Union(fatherCur,fatherPre);
//表示数量也进行了合并
count[fatherPre] = count[fatherPre] + count[fatherCur];
if(count[fatherPre] > ans){
ans = count[fatherPre];
}
}
}
return ans-1;
}
int findFather(int x){
if(father[x] == x){
return x;
}else{
int tmp = findFather(father[x]);
father[x] = tmp;
return tmp;
}
}
void Union(int cur,int pre){
father[cur] = pre;
}
};
码后反思
- 网友的并查集的思想比我的更好:
他是以当前结点作为基准,去寻找后继结点的。而我是以当前结点作为基准,去寻找前驱结点。然而我们的并查集的概念是一致的,最终导致我的思路要绕一些弯。 - 修改思路后的代码:
//时间复杂度为O(n)这就很恐怖,不能使用动态规划了 class Solution { private: //father数组,初始化为自己 //使用unordered map来存储 unordered_map<int,int> father; //每棵树的结点的个数,表示从该结点开始的最小序列的大小 unordered_map<int,int> count; //最大连通分量的大小 int ans; public: int longestConsecutive(vector<int>& nums) { if(nums.size() == 0){ return 0; } //初始化两个数组 //初始化father for(auto i : nums){ father[i] = i; count[i] = 1; } ans = 1; for(auto i : nums){ //对cur进行映射,cur应该是从1开始的 int cur = i; int next = i+1; int fatherCur = findFather(cur); if(father.count(next) == 0){ continue; }else{ int fatherNext = findFather(next); if(fatherCur == fatherNext){//说明已经在一个并查集里面了,不需要进行操作,考虑了重复数字的情况 continue; }else{ //如果不在一个并查集里面,需要进行合并 //将小的数合并到大的数的集里面 Union(fatherCur,fatherNext); //表示数量也进行了合并 count[fatherCur] = count[fatherNext] + count[fatherCur]; if(count[fatherCur] > ans){ ans = count[fatherCur]; } } } } return ans; } int findFather(int x){ if(father[x] == x){ return x; }else{ int tmp = findFather(father[x]); father[x] = tmp; return tmp; } } void Union(int cur,int next){ father[next] = cur; } };
- 需要注意,判断
unordered_map
里面有没有一个key
得使用count()
,不能用key
对应的mp[key]==0
来判断,原因很显然,不要投机取巧! - 并查集的方法其实有点蠢,使用C++ STL的
unordered_map
自带的hash功能,就能很快的实现这个功能。看来自己是被并查集(或者是自己的思想)束缚住了,显然并查集是平白地在hash
的基础上增加了hash
的工作量! - 这道题最大的收获应该是掌握了hash的手法
unordered_map
,受教了!