标签:
题目来源,待字闺中,原创@陈利人 ,欢迎大家继续关注微信公众账号“待字闺中”
给一个整数数组a[]。 找到当中包括最多连续数的子集,比方给:15, 7, 12, 6, 14, 13, 9, 11,则返回: 5:[11, 12, 13, 14, 15] 。
分析:最简单的方法是sort然后scan一遍,可是要 o(nlgn) , 有什么 O(n) 的方法吗?网上有人用map或set来做。但map或set的复杂度还是O(nlgn)。并查集能够做到O(n),此题用并查集比較简单。
下面參考http://www.2cto.com/kf/201308/234796.html,可是该文章给的代码有缺陷。就是仅仅能处理正数的情况
并查集是一宗简单的用途广泛的算法和数据结构。并查集是若干个不相交集合。可以实现较快的合并和推断元素所在集合的操作。应用非常多,比方:求无向图的连通分量个数,实现kruskal算法等。
并查集能够方便地进行下面三种操作:
1、Make(x):把每个元素初始化为一个集合,初始化后每个元素的父节点就是它本身。
2、Find(x):查找一个元素所在的集合,一个元素所在的集合用这个集合的祖先节点来标识。
推断两个元素是否属于同一个集合,仅仅要看他们所在集合的祖先节点是否同样就可以。
3、Union(x, y):合并x、y所在的两个集合。先利用Find()找到两个集合的祖先,若这两个祖先节点不是同一个节点,将当中一个祖先节点指向还有一个祖先节点就可以。(详细哪个祖先指向哪个祖先能够依据实际情况而定)
并查集的优化:在Find函数中,每次找祖先节点的复杂度是O(n)。当我们经过递归找祖先节点的时候。顺便把这条路径上的全部子孙节点都直接指向祖先。这样下次Find的时候复杂度就变成了O(1)。
回到题目,首先调用Make(x)将每一个元素变成一个并查集,然后一次扫描a[i],查看a[i]-1是否存在,若存在调用Union(a[i], a[i]-1)。查看a[i]+1是否存在,若存在调用Union(a[i]+1, a[i])。在合并的同一时候更新集合的大小。
接下里的问题是怎么推断a[i]-1和a[i]+1是否存在,用哈希能够解决,并且复杂度是O(1)。
该题中并查集的操作都是基于下标的。我们用p[i]表示a[i]的父节点的下标,用len[i]表示以a[i]为根的集合的大小。我们合并的时候总是将较小值集合的祖先指向较大值集合的祖先。这样就仅仅须要记录较大值集合的祖先节点相应的长度。
最后扫描数组a[]和len[]。找到最大长度maxLen相应的a[i]。最后的结果就是:a[i]-maxLen+1, a[i]-maxLen+2, ..., a[i]。
详细见代码。该代码使用map做hash表,比原文的vector优越的地方就是能够处理负数
#include <iostream> #include <map> #include <vector> #include <assert.h> using namespace std; void Make(vector<int>& p,vector<int>& len,int x) { p[x] = x; len[x] = 1; } int Find(vector<int>& p,int x) { if(p[x] != x)p[x] = Find(p,p[x]); return p[x]; } //保证x >= y void Union(vector<int>& p,vector<int>& len,int x,int y) { int p1 = Find(p,x); int p2 = Find(p,y); if(p1 == p2)return; p[p2] = p1; len[p1] += len[p2]; } bool exist(map<int,int>& hash,int value,int x) { map<int,int>::iterator iter = hash.begin(); while(iter != hash.end()) { if(iter->first == value && iter->second == x)return true; iter ++; } return false; } bool exist(map<int,int>& hash,int value) { map<int,int>::iterator iter = hash.begin(); while(iter != hash.end()) { if(iter->first == value)return true; iter ++; } return false; } void Longest(vector<int>& ivec, int& max, int& maxLen) { assert(!ivec.empty()); int size = ivec.size(); vector<int> p(size);//并查集的父亲下标 vector<int> len(size);//该节点为根的并查集的大小 map<int,int> hash;//用于推断某一值的相邻元素是否存在 int i; for(i=0;i<size;i++)Make(p,len,i); for(i=0;i<size;i++) hash[ivec[i]] = i;//假设有反复数据,后面的数据会把前面的数据覆盖。这样能够防止反复计算 for(i=0;i<size;i++) { if(exist(hash,ivec[i],i))//加上i用于处理反复数字 { if(exist(hash,ivec[i]-1))Union(p,len,i,hash[ivec[i]-1]);//此处不须要处理反复数字是由于hash[ivec[i]-1]选中的肯定是最后一个 if(exist(hash,ivec[i]+1))Union(p,len,hash[ivec[i]+1],i);//交换位置保证Union的x > y } } max = ivec[0]; maxLen = len[0]; for(i=1;i<size;i++) { if(len[i] >maxLen)</span> { max = ivec[i]; maxLen = len[i]; } } } int main() { int n; while(cin >> n) { vector<int> data(n); int i; for(i=0;i<n;i++)cin >> data[i]; int max,maxLen; Longest(data,max,maxLen); for(i=1;i<=maxLen;i++)cout << max - maxLen + i << " "; cout << endl; } return 0; }如有错误,请指正,谢谢
标签:
原文地址:http://www.cnblogs.com/lcchuguo/p/5384578.html