题目一:
http://topic.youkuaiyun.com/u/20070929/14/b183cd03-d780-4c59-a666-ab127f12f7b1.html
有2.5亿个整数(这2.5亿个整数存储在一个数组里面,至于数组是放在外存还是内存,没有进一步具体说明);
要求找出这2.5亿个数字里面,不重复的数字的个数;
另外,可用的内存限定为600M;
要求算法尽量高效,最优;
(不重复的数字的个数,可以理解为:可以理解为重复的数只记一次,也可以理解为重复的数不计算)
如果是第一种理解的话,那么定义一个int变量count(初值为2.5亿)和位数字bit[512M],遍历这些数,如果当前的数i已出现过,count--;遍历完以后,count的值就是只出现过一次的数的个数了。
如果是第二种理解,那么首先要将2.5亿个int数读入内存,那么需要1G,而内存只有600M,所以不可能一次将数据处理完,必须分批进行处理。如果采用位数组,那么每一个数应该有2个位表示,因为每一个数有3种状态:未出现,出现一次,出现多次。
内存必须分为两份:1:存储位数组 2:存储2.5亿个整数。
存储位数组:因为内存不够,无法一次表示所有的整数,所以分两次处理2.5亿个数。第一次处理正整数,计算只出现一次的正整数;第二次处理负整数,计算只出现一次的负整数,那么存储整数的空间为512M,那么内存还剩下88M。
存储整数:1个整数4B,2.5亿总共10亿B。因为内存总共600M,所以1g/0.5g=2,所以分2次即可以解决。但是,还需要内存空间来存放位数组,所以必须平衡位数组的大小和存放数据的大小。
如何平衡:256M的内存存放位数组,256M的内存存放待处理数据。分4次将数据读入内存,第一次读入最高两位为11的,第二次读入最高两位为10的,第三次读入最高位为01,第四次读入最高位为00的。因为分四次读入,每一次读入的数据范围为4g/4=1g,每一个数用2位表示,那么位数组刚好1g/8*2=256M。
Answer1:
http://topic.youkuaiyun.com/u/20070929/14/b183cd03-d780-4c59-a666-ab127f12f7b1.html
就这么道题目,至于用平衡二叉树嘛
他这个题目出的恶心,说什么2.5亿,其实就算说2500亿,也没有关系的,因为它们是放在外存上的。
这个问题仅仅是需要计算特定值的数量,所以不需要用排序,也不需要大的空间来保存值本身,只要计数就可以了。
为了判断数是否重复,就需要用一个标记来标志某个数是否出现过,这个可以用桶算法来完成(参考一下桶排序,很有名的,这个算法的关键就是在于对每一个值都进行计数,对于有限值域的数组可以有比较高的排序速度),这样,真正影响内存使用的,是int型的字长,对于32位的int,可以取4g个不同的值,它需要的桶就是512MByte的空间,每个值一位标志。
在这样的情况下,会发现一位标志不够算法使用,因为算法需要有3个标志,分别是:未出现过,第一次出现,多次出现;因此,可以采用18楼的分治法,对不同值域的数,分两次处理,这样每个值就可以有两位来作标志,同样只要512mbyte的空间
这样算法就出来了
(这种方法是计算只有出现一次的元素个数,比如:3,2,2。 那么出现一次的个数为1)
1 定义一个长度为4g的位数组flag(512M),清0,计数器cnt清0
2 循环 读int值 x,
3
if(x>=0)
if(!flag[x*2])
flag[x*2]=1,flag[x*2+1]=0,cnt++; //当数据第一次出现时,计数
else
if(!flag[x*2+1])
flag[x*2+1]=1,cnt --; //当数据第二次出现时,减少计数,
4 如果还有数据,则回到2
5 flag[]清0
6 循环 读int值 x,
7
if(x <0)
if(!flag[(-1-x)*2])
flag[(-1-x)*2]=1,flag[(-1-x)*2+1]=0,cnt++;
else
if(!flag[(-1-x)*2+1])
flag[(-1-x)*2+1]=1,cnt --;
8 如果还有数据,则回到6
最后,cnt值就是需要的数值
Answer2:
是只计算个数,而不要求知道是那些数字
所以最简单的办法其实是这样的
数字进行无格式处理(这样可以一一对应地址)
定义两个256M的位数组a[],b[],表示0-2^30位数字的对应关系,
计数器cnt=0;
flag a[2^31]={0};
flag b[a^31]={0};
初始化两个数组全为0
第一次扫描,
循环遍历{
if(x <2^31){
if (a[x]==1&&b[x]==0) {b[x]=1;cnt--;}
if (a[x]==0) {a[x]=1;cnt++;}
}
}
3.循环1,2处理完所有数字,如果输入数允许删除,可以在这里做已处理数字的删除操作,不处理也没有多大问题。
第二次扫描,
重新对a[]和b[]填充0;
第二次扫描
循环{
if(!(x <2^31)}{
if (a[x-2^31]==1&&b[x-2^31]){b[x-2^31]=1;cnt--;}
if ([x-2^31]==0 ){a[x-2^31]=1;cnt++;}
}
}
这样只需要两次循环就搞定了,复杂度是一个常数啊。
Answer3:(感觉有问题)
看来问题和各位的见解,其实我个人觉得不用那么麻烦吧。用HASH映射一下,只是这个HASH不能冲突。就算2.5个数字全部都不一样的也都能解决。
2.5亿位bit标志2.5亿个数是否出现过。
2.5亿bit也就不到30M的空间。
这样剩下的500多M可以用来读取数字,一个INT算32位的话,也就4个字节
我一次读1.25亿个数也不到500M的空间。大文件的读取可以采用内存映射的方法,加快读取速度,这样减少了IO读取时间。我的步骤是
1.分配30M内存空间,全部置0,读取文件内容一次1.25亿个具体不限制,利用HASH函数(n=k)n是数组的位置,k是读取到的数
如果为0则置为1,1的话置2,2的话就不管了。
2.再扫描一次,0的表示没出现过的,1的表示只出现一个的,2的表示至少出现过2次的。这里只统计1的个数。
时间效率也就2n,应该可以接受。
其实如果内存限制的话,并不用读取1.25亿个一次。根据内存大小读取多次,只是增加了IO时间而已。
至于上面各位说的,平衡数想法很好,就是效率不高吧。排序的也这里用不着。请大家赐教。
Answer4:
兼回superdullwolf的问询
-----------------------
其实我的算法和18楼的是一样的,两个数值就是为了记录一个数字的三种状态的方法,b[]相当于a[]的对应高位,我用到了三种状态
状态 1 2 3
------------------
a[] 0 1 1
b[] 0 0 1
------------------
这样一个数是否已经统计就涉及这样三个状态的转换,要记录一组数中只出现一次的数的个数count是这样的过程进行统计的
if (a[x]==0) {count++;a[x]=1;}
else if (a[x]==1&b[x]==0) {count--;b[x]=1;}
else if (b[x]){;}
这样所有的数遍历一次后count就是所求的了,但这要求a[]和b[]的空间要求满足数值的范围,这正是这个题目的难点,空间是不足的,只能满足部分啊,18楼就犯了这样错误,通过计算,我们知道只需要对数值范围分成两组就可以了,范围分组仅仅是涉及最高位,这就是我的算法要点了。
我前面把那个逻辑判断进行了优化,这样就减少了else的出现,就成了:
if (a[x]==1&&b[x]==0) {count--;b[x]=1;}
if (a[x]==0) {count++;a[x]=1;}
也可以写成
if (a[x]&&!b[x]) {count--;b[x]=1;}
if (!a[x]) {count++;a[x]=1;}
-----------------------------------------------------------------------------------------------------------------------
所以我的算法是0(n)的算法,只需要遍历2次。
Answer5:
2.5亿,理论上结果数据(重复)最大为500M,而全部整形的状态表示为512M,所以不可能利用600M可用内存一次性处理全部结果,写外部文件是必须的
18楼的算法是可行的,考虑到是考试题,应该还有优化的可能,两次扫描的成本(如此大的数据量)是不是太高?
提供一个想法,不一定更好
1分配512兆存储空间(M),用一个bit表示一个整形的状态:,初始化(M)为0
2读取数据,若0,置位1;若1,重复,加入随机结果数据,必要时(count达到一定量)写入结果文件(F)(毕竟还有几十M空间可用)
3第二步结束,所有重复的数据(其中还有重复)已经全部写入文件(F),理论上最坏为2.5亿-1
4重新初始化(M)为0,读取结果文件(F)数据,置位1,即为最终结果
在数据均匀分布时,效果应该优于2次扫描
在2次以上重复数据特别多时,写文件的成本增加,但是扫描数据的成本 <2.5亿*2
Answer6:
回:xwy6509
内存地址是连续的
判断一个整数的标志:
1、该整数值/8(二进制右移3位),即为地址偏移值,加上分配内存的首字节地址,即为实际存储地址(byte)
2、该整数值%8(取该整数最后一字节的最后三位),即为该整数在字节中flag位置(0-7 :bit)
3、通过位运算&查询状态,|设置状态,位掩码为0x80,0x40,0x20,0x10,...0x01
简单的加减和移位、位运算,是最高效的
排序算法不好的原因有二:
1、需要极大的存储空间
2、需要更多的运算判断
Answer7:
有点意思! 难度在于一维的bit 无法保持有无以外的第三状态
我的想法
先开512BIt 然后 再开30 M bit 保存2.5 数据对应的位置
第一次 读数据,没有置1有的话,在30M 相应的位数上设置1
第二次 再读一次, 30M bit 有1 的 就把512M 相应的设置为0
读完 512bit 里面就是所有没重复的数字
读一下有多少 就有结果了。
Answer8:
按照oo的说法来做,很难去求只出现一次的数的个数,因为 一个bit只能表示两种状态, 只能表示相应的数有或者没有,假如我们为每个数设置 2 bits 来表征它的状态就好了,这样这个flag的取值可以为 0,1,2,3,0表示该数不存在,1表示存在一次,2表示存在多次, 但是这样的话, 需要1G的空间来存放flag, 那么可以分开来,遍历2.5亿数据两次,第一次我只算正数,第二次我只算负数,每次都还是申请512M空间。
1. 申请512M空间, 都清零
2. 首先遍历2.5亿数据,假如为正数,那么 检查该数对应flag, 假如为0,则置1,count++, 假如flag为1,则置2, count--, 假如flag为2,则继续
3.再遍历2.5亿数据,假如为负数,那么使用相同方法来检测
4. 最后的count就是结果了。
Answer9:
BYTE marks[2^29];//开一个512M的数组标记
BYTE repmarks[2^25];//32M 32M*8>2.5开一个32M的数组标记重覆数字所在传入数组的位置
const DWORD exambufs[]={1,2,2};
const BYTE bitmarks[8]={1,2,4,8,16,32,64,128};
DWORD Calc1TimeNum(DWORD *pBuf,DWORD bufcount)
{
DWORD dw ;
DWORD count = 0 ;//计算所有出现过的数字
DWORD count2 = 0 ;//计算只重复出现的数字
memset(marks,0,sizeof(marks));
memset(repmarks,0,sizeof(repmarks));
ASSERT(sizeof(repmarks)*8>=bufcount);
for(dw=0;dw <bufcount;dw++)
{
if(marks[pBuf[dw]>>3]&bitmarks[pBuf[dw]&7])
{
repmarks[dw>>3] |= bitmarks[dw&7];//把重复的位置标出来
}
else
{
count ++;
marks[pBuf[dw]>>3] |= bitmarks[pBuf[dw]&7];
}
}
memset(marks,0,sizeof(marks));
for(dw=0;dw <bufcount;dw++)
{
if(repmarks[dw>>3] & bitmarks[dw&7])//判断到重复标志
{
if(marks[pBuf[dw]>>3]&bitmarks[pBuf[dw]&7])
{
}
else
{
count2 ++;
marks[pBuf[dw]>>3] |= bitmarks[pBuf[dw]&7];
}
}
}
return count-count2;
}
Answer10:
有想法还是得有编码
闲时做做这样的习题还是不错的
简单优化一下,扫描数据的时间节约了一倍:25秒!
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <memory.h>
int ParseBy512()
{
static unsigned char nBit0[8]={0x7F,0xBF,0xDF,0xEF,0xF7,0xFB,0xFD,0xFE};
static unsigned char nBit1[8]={0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01};
FILE *f=fopen("g://test.dat","rb");
if(f == NULL) return -1;
unsigned char *bufA=new unsigned char[536870912];//512M
unsigned char *bufB=new unsigned char[31250000];//30M
unsigned int *bufC=new unsigned int[8];
memset(bufA,0,536870912);
memset(bufB,0,31250000);
unsigned int nNumber;
unsigned char n;
unsigned int i=0;
unsigned int j=0;
printf("buf alloc ok/r/n");
time_t tmb;
time(&tmb);
for(;i <31250000;i++)
{
fread(bufC,sizeof(unsigned int),8,f);
for(j=0;j <8;j++)
{
n = bufC[j] & 0x7;
nNumber =bufC[j] >> 3;
if(bufA[nNumber] & nBit1[n])
{
bufB[i] |= nBit1[j];
bufA[nNumber] &= nBit0[n];
}
else
{
bufA[nNumber] |= nBit1[n];
}
}
}
time_t tm1;
time(&tm1);
printf("scan data ok! %d sec/r/n",tm1-tmb);
for(i=0;i <31250000;i++)
{
for(n=0;n <8;n++)
{
if(bufB[i] & nBit1[n])
{
fseek(f,((i < <3)+n)*sizeof(unsigned int),SEEK_SET);
fread(&nNumber,sizeof(unsigned int),1,f);
bufA[nNumber >> 3] &= nBit0[nNumber & 0x7];
}
}
}
fclose(f);
time_t tm2;
time(&tm2);
printf("treat data ok! %d sec/r/n",tm2-tm1);
f=fopen("g://result.dat","w+");
if(f == NULL) return -1;
int iRsult=0;
for(i=0;i <536870912;i++)
{
for(n=0;n <8;n++)
{
if(bufA[i] & nBit1[n])
{
nNumber = (i < <3) + n;
fwrite(&nNumber,sizeof(unsigned int),1,f);
iRsult ++;
}
}
}
fclose(f);
delete bufA;
delete bufB;
delete bufC;
time_t tme;
time(&tme);
printf("time:%d sec,Rsult=%d/r/n",tme-tmb,iRsult);
return 0;
}
int GenrateData()
{
FILE *f=fopen("g://test.dat","w+");
if(f == NULL) return -1;
unsigned int nNumber;
srand(time(NULL));
time_t tmb;
time(&tmb);
for(int i=0;i <250000000;i++)
{
nNumber = rand();
nNumber < <= 16;
nNumber += rand();
fwrite(&nNumber,sizeof(unsigned int),1,f);
}
fclose(f);
time_t tme;
time(&tme);
printf("time:%d sec:i=%d",tme-tmb,i);
return 0;
}
int main(int argc, char ** argv)
{
if(argc > 1) GenrateData();
else
{
ParseBy512();
FILE *f=fopen("g://result.dat","rb");
if(f == NULL) return -1;
unsigned int nNumber;
for(int i=0;i <20;i++)
{
fread(&nNumber,sizeof(unsigned int),1,f);
printf("%u/r/n",nNumber);
}
for(i=1;i <=20;i++)
{
fseek(f,-(i*4),SEEK_END);
fread(&nNumber,sizeof(unsigned int),1,f);
printf("%u/r/n",nNumber);
}
fclose(f);
}
return 0;
}
题目二:
有10亿个整数,要求选取重复次数最多的100个整数
Answer1:
http://wbfeixue.blog.163.com/blog/static/83299901200811334432834/
下面是一种解答,如果不愿意看的话,很简单,遍历数组,然后用hash表统计,value为出现的次数,然后findmax 100次,下面的第五问解答很好
要解答这个问题,首先要弄清楚下面几个条件。
(1)有内存限制吗?
(2)整数的范围是多少?有符号,无符号,32位还是64位?
(3)整数集的内容大吗?(即出现的整数空间的大小大吗?)
(4)如果只需要求模糊解,怎么解?
(5)求数组中的第k大元素?
(6)相关问题:求一个整数列中出现次数最多的整数
(7)相关问题:有一个整数数组,请求出两两之差绝对值最小的值,记住,只要得出最小值即可,不需要求出是哪两个数。
(1)如果没有内存限制,且假设是32位无符号的整数。最方便的办法就是建立一个整形数组,int hash[2^32](赞不考虑程序的虚地址空间上限),然后对这10亿个数进行一次遍历,这样,可以得到这2^32个数各自出现的次数,再对这个hash数组进行取第k大元素,100次后,就可以取出这出现次数最多的前100个数。遍历10亿个数的时间复杂度是O(n),n=10^10,求第k大元素的时间复杂度是O(m),m=2^32(=4294967296),那么本算法的时间复杂度是O(n),空间复杂度是O(s),s=2^32。内存要2^32*4=16G
(2)如果有内存限制,或者必须满足程序虚地址空间上限。那么可以对整数空间进行分段处理,比如只提供512M内存,则将2^32个整数划分成32个空间0~2^(27)-1,2^(27)~2^(28)-1,...,31*2^(27)~2^(32)-1。对原来的10亿个数遍历32次,每次遍历,得到每个空间的整数的出现次数,并求出此空间中,出现次数最多的前100个整数,保存下来。这样32次之后,就得到了出现次数前3200的整数,再对这3200个整数取第k大元素,得到出现次数最多的前100个整数。这个算法的时间复杂度也是O(n),空间复杂度降低多少不知道,但是内存使用降低不少。
(3)如果整数空间比较小,也就是说这10亿个数中有很多重复的数,最方便的办法估计就是维护一个HashTable对象ht,key就是整数值,value就是该整数值出现的次数。遍历这10亿个元素,得到ht后再对这个ht求第k大元素。那么这个算法的时间复杂度就是O(n),n=10^10,空间复杂度是O(m),m为整数空间大小。
(4)随机采样(或者将原来的顺序打乱,然后再顺序采样)。对样本中的整数进行出现次数的统计,这个时候采用HashTable的办法最好,时间复杂度是O(n)。如果对使用的空间有所限制,那么只能对该样本进行排序,再对排序后的样本进行100次遍历得到出现次数最多的前100个整数,则时间复杂度是O(nlogn),空间复杂度是O(1)。
(5)好像有两种算法。假设要求数组a[1...n]中第k大元素。
(a)递归快排算法。若n<44(经验值)则直接排序返回第k大元素,否则,将1到n分成n/5个组,每个组5个元素,然后求这n/5个组的每组的中项元素,再求这n/5个中项元素的中项元素mm(注意,这里也可以用递归调用自身的方法)。然后对数组a根据mm分成三组,a1中的所有元素小于mm,a2中的所有元素等于mm,a3中的所有元素大于mm,如果|a1|>=k,则第k大元素在a1中,如果|a1|+|a2|>=k|a1|,则第k大元素就是mm,如果k>|a1|+|a2|,则第k大元素在a3中,再继续递归调用。这个算法的时间复杂度是O(n)。(注意,这里的中项mm也可以随机选择a中的元素,其时间复杂度也近似于O(n),而且系数也比较小)。
(b)基于位查找(仅对于无符号整数的查找)。将32位整数的二进制位分为4段,每段8位,先比较a中所有元素高8位,找出第k大元素高8位的范围,再对应这高8位的范围在次高八位中找第k大元素的范围,...这样4次之后就可以找到第k大元素的。可以举个例子便于理解,在10个3位整数中找第k大元素,将3位分成3段,每段1位,每位之可能是0,1。如果这10个数的最高位0的个数m大于等于k,则第k大元素的最高位为0,再在最高位为0的元素中找次高位为第k大元素;如果10个数中最高位0的个数m大于k,则在最高位为1的元素中找此高位为第m-k大元素。...
(6)这个问题是前面那个问题的特例。有没有特殊的解法使效率又提高一些呢?我觉得没有,因为1和100本来就是常数级,和n比它们的差别是忽略不计的。
(7)简单的解法是对这个数组排序,然后再对排好序的数组进行一次遍历就可得到两两绝对值最差的最小值,时间复杂度是O(nlogn)。网上说求a的min,max和长度n,如果Dmax = (max-min+1)/n = 0,那么就说明数组a中有重复的元素,直接返回0。但是如果Dmax = (max-min+1)/n > 0,那么就以Dmax为箱的长度装入a的元素,再在箱内和箱间比较。我不懂为什么,但是这个空间复杂度是O(max),而且好像如果a是1, 2, 3...100,那么Dmax就是1了,那么a不是没有动吗?还有人说够找数组b,b[i] = a[i] - a[i+1],则a[i]-a[j]=b[i]+b[i+1]+...+b[j-1]也不知下文了,看来这个题比较搞啊。就是奥赛题,BS。