zoukankan      html  css  js  c++  java
  • 待字闺中之最多连续数的子集

    题目来源,待字闺中,原创@陈利人 ,欢迎大家继续关注微信公众账号“待字闺中”

    给一个整数数组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;
    }
    如有错误,请指正,谢谢


  • 相关阅读:
    什么样的代码称得上是好代码?
    九年程序人生 总结分享
    Docker入门 第一课 --.Net Core 使用Docker全程记录
    阿里云 Windows Server 2012 r2 部署asp.net mvc网站 平坑之旅
    Visual studio 2015 Community 安装过程中遇到问题的终极解决
    Activiti6.0 spring5 工作流引擎 java SSM流程审批 项目框架
    java 进销存 库存管理 销售报表 商户管理 springmvc SSM crm 项目
    Leetcode名企之路
    24. 两两交换链表中的节点
    21. 合并两个有序链表
  • 原文地址:https://www.cnblogs.com/lcchuguo/p/5384578.html
Copyright © 2011-2022 走看看