算法面试题

http://blog.csdn.net/samjustin1/article/details/52251180 

http://blog.csdn.net/samjustin1/article/details/52251180


http://blog.csdn.net/eastmount/article/details/48944443

http://www.cnblogs.com/BBys/p/5906808.html

 /*

16位编译器 char :1个字节 char*(即指针变量): 2个字节 short int : 2个字节 int: 2个字节 unsigned int : 2个字节 float: 4个字节 double: 8个字节 long: 4个字节 long long: 8个字节 unsigned long: 4个字节

32位编译器

char :1个字节 char*(即指针变量): 4个字节(32位的寻址空间是2^32, 即32个bit,也就是4个字节。同理64位编译器) short int : 2个字节

int: 4个字节 unsigned int : 4个字节 float: 4个字节 double: 8个字节 long: 4个字节 long long: 8个字节 unsigned long: 4个字节 64位编译器 char :1个字节 char*(即指针变量): 8个字节 short int : 2个字节 int: 4个字节 unsigned int : 4个字节 float: 4个字节 double: 8个字节 long: 8个字节 long long: 8个字节 unsigned long: 8个字节


*/

 IP地址是32位的二进制数,所以共有N=2^32=4G个不同的IP地址, 创建一个unsigned count[N];(N=4G)的数组,即可统计出每个IP的访问次数,而sizeof(count) == 4G*4=16G, 远远超过了32位计算机所支持的内存大小,


假设允许使用的内存是512M,  512M/4=128M 即512M内存可以统计128M个不同的IP地址的访问次数.而N/128M =4G/128M = 32 ,所以只要把IP地址划分成32个不同的区间,分别统计出每个区间中访问次数最大的IP, 然后就可以计算出所有IP地址中访问次数最大的IP了.


因为2^5=32, 所以可以把IP地址的最高5位作为区间编号, 剩下的27为作为区间内的值,建立32个临时文件,代表32个区间,把相同区间的IP地址保存到同一的临时文件中.


例如:

ip1=0x1f4e2342

ip1的高5位是id1  =  ip1 >>27 = 0x11 = 3

ip1的其余27位是value1  =  ip1 &0x07ffffff = 0x074e2342

所以把 value1 保存在tmp3文件中.

由id1和value1可以还原成ip1, 即 ip1 =(id1<<27)|value1

    按照上面的方法可以得到32个临时文件,每个临时文件中的IP地址的取值范围属于[0-128M),因此可以统计出每个IP地址的访问次数.从而找到访问次数最大的IP地址

==============================================

  1. #include <fstream>  
  2. #include <iostream>  
  3. #include <ctime>  
  4.   
  5. using namespace std;  
  6. #define N 32           //临时文件数  
  7.   
  8. #define ID(x)  (x>>27)                 //x对应的文件编号  
  9. #define VALUE(x) (x&0x07ffffff)        //x在文件中保存的值  
  10. #define MAKE_IP(x,y)  ((x<<27)|y)      //由文件编号和值得到IP地址.  
  11.   
  12. #define MEM_SIZE  128*1024*1024       //需分配内存的大小为 MEM_SIZE*sizeof(unsigned)     
  13.   
  14. char* data_path="D:/test/ip.dat";        //ip数据  
  15.   
  16.  //产生n个随机IP地址  
  17. void make_data(const int& n)         
  18. {  
  19.     ofstream out(data_path,ios::out|ios::binary);  
  20.     srand((unsigned)(time(NULL)));  
  21.     if (out)  
  22.     {  
  23.         for (int i=0; i<n; ++i)  
  24.         {  
  25.             unsigned val=unsigned(rand());           
  26.             val = (val<<24)|val;              //产生unsigned类型的随机数  
  27.   
  28.             out.write((char *)&val,sizeof (unsigned));  
  29.         }  
  30.     }  
  31. }  
  32.   
  33. //找到访问次数最大的ip地址  
  34. int main()  
  35. {  
  36.     //make_data(100);     //   
  37.     make_data(100000000);       //产生测试用的IP数据  
  38.     fstream arr[N];  
  39.       
  40.     for (int i=0; i<N; ++i)                 //创建N个临时文件  
  41.     {  
  42.         char tmp_path[128];     //临时文件路径  
  43.         sprintf(tmp_path,"D:/test/tmp%d.dat",i);  
  44.         arr[i].open(tmp_path, ios::trunc|ios::in|ios::out|ios::binary);  //打开第i个文件  
  45.   
  46.         if( !arr[i])  
  47.         {  
  48.             cout<<"open file"<<i<<"error"<<endl;  
  49.         }  
  50.     }  
  51.   
  52.     ifstream infile(data_path,ios::in|ios::binary);   //读入测试用的IP数据  
  53.     unsigned data;  
  54.   
  55.     while(infile.read((char*)(&data), sizeof(data)))  
  56.     {  
  57.         unsigned val=VALUE(data);  
  58.         int key=ID(data);  
  59.         arr[ID(data)].write((char*)(&val), sizeof(val));           //保存到临时文件件中  
  60.     }  
  61.   
  62.     for(unsigned i=0; i<N; ++i)  
  63.     {  
  64.         arr[i].seekg(0);  
  65.     }  
  66.     unsigned max_ip = 0;    //出现次数最多的ip地址  
  67.     unsigned max_times = 0;     //最大只出现的次数  
  68.   
  69.     //分配512M内存,用于统计每个数出现的次数  
  70.     unsigned *count = new unsigned[MEM_SIZE];    
  71.   
  72.     for (unsigned i=0; i<N; ++i)  
  73.     {  
  74.         memset(count, 0, sizeof(unsigned)*MEM_SIZE);  
  75.   
  76.         //统计每个临时文件件中不同数字出现的次数  非常棒好好学习学习
  77.         unsigned data;  
  78.         while(arr[i].read((char*)(&data), sizeof(unsigned)))       
  79.         {  
  80.             ++count[data];  
  81.         }  
  82.           
  83.         //找出出现次数最多的IP地址  
  84.         for(unsigned j=0; j<MEM_SIZE; ++j)                             
  85.         {  
  86.             if(max_times<count[j])             
  87.             {  
  88.                 max_times = count[j];  
  89.                 max_ip = MAKE_IP(i,j);        // 恢复成原ip地址.  
  90.             }  
  91.         }  
  92.     }  
  93.     delete[] count;  
  94.     unsigned char *result=(unsigned char *)(&max_ip);  
  95.     printf("出现次数最多的IP为:%d.%d.%d.%d,共出现%d次",   
  96.         result[0], result[1], result[2], result[3], max_times);  
  97. }

===============================================

#define MEM_SIZE  128*1024*1024       //需分配内存的大小为 MEM_SIZE*sizeof(unsigned)

  1. //分配512M内存,用于统计每个数出现的次数 

  2.  
  3.     unsigned *count = new unsigned[MEM_SIZE];    
  4. =====================================================
    1. //统计每个临时文件件中不同数字出现的次数  
    2.         unsigned data;  
    3.         while(arr[i].read((char*)(&data), sizeof(unsigned)))       
    4.         {  
    5.             ++count[data];  
    6.         }  

=================================================

ifstream in("xxx.xxx");

in.read((unsigned char*)n,sizeof(n));//从xxx.xxx中读取指定个整数,注意类型转换

====================================

因为不知道文件的大小,担心超出buffer的容量或者不足本以为用readsome是可以实现的,不过调试发现readsome
总是返回0,什么都没有读到。后来查了一下下面的资料
1.由一个Bug看ifstream中read()与readsome()的区别
http://www.softat.org/viewthread.php?tid=59086
2.输入流的读取方法read()、readsome()比较
http://www.cnblogs.com/kb/archive/2005/08/15/215346.html
 
具体说说自己理解的原因
在内存快的时候readsome和read是有着相同的效果的,所以在msdn中的readsome的例子一般都是使用了内存中的流
来实现,可是在文件中,文件流读取的时候首先要把文件从硬盘读取到一个内存中的buffer1)中,每一次调用
read或者readsome是从内存中读取数据

read和readsome的不同点在于,read每次读完内存后,如果还能往输出内存buffer2(2表示和1的区别)中写入,还会更新
buffer1(从文件中读取后输入到buffer1中),而readsome仅仅对buffer1中的数据进行读取。并不会刷新buffer1
所以我们可以看出在fstream中readsome的使用时不合适的
我们因该使用read,对于readsome中返回的值,我们可以在read后用gcount函数获得。具体代码如下

     
     
  ifstream doc_file_stream;   doc_file_stream.open( " c:/test.doc " ,::std::ios:: in );    char  buffer[ 1024 ];       do    {     if(doc_file_stream.bad())     {       break;     }     int read_num = 0;          doc_file_stream.read(buffer,1023);     read_num = doc_file_stream.gcount();    buffer[read_num] = 0;     if(read_num > 0){       cout << buffer ;     }     else{       break;     }   }    while ( true );   doc_file_stream.close();

22222222222222222222222222222222222222222222222222222222222222222222222222222222222

22222222222222222222222222222222222222222222222222222222222222222222222222222222222

22222222222222222222222222222222222222222222222222222222222222222222222222222222222

  但如果同一个元素重复出现在不同的电脑中呢,如下例子所述:

      

这个时候,你可以有两种方法:
  • 遍历一遍所有数据,重新hash取摸,如此使得同一个元素只出现在单独的一台电脑中,然后采用上面所说的方法,统计每台电脑中各个元素的出现次数找出TOP10,继而组合100台电脑上的TOP10,找出最终的TOP10。
  • 或者,暴力求解:直接统计统计每台电脑中各个元素的出现次数,然后把同一个元素在不同机器中的出现次数相加,最终从所有数据中找出TOP10。

 33333333333333333333333333333333333333333333333333333333333333333333333

3333333333333333333333333333333333333333333333333333333333333333333333333333

有10个文件,每个文件1G,每个文件的每一行存放的都是用户的query,每个文件的query都可能重复。要求你按照query的频度排序。

方案1:

  s 顺序读取10个文件,按照hash(query)的结果将query写入到另外10个文件(记为 )中。这样新生成的文件每个的大小大约也1G(假设hash函数是随机的)。
  s 找一台内存在2G左右的机器,依次对 用hash_map(query, query_count)来统计每个query出现的次数。利用快速/堆/归并排序按照出现次数进行排序。将排序好的query和对应的query_cout输出到文件中。这样得到了10个排好序的文件(记为 )。
  s 对 这10个文件进行归并排序(内排序与外排序相结合)。
方案2:
  一般query的总量是有限的,只是重复的次数比较多而已,可能对于所有的query,一次性就可以加入到内存了。这样,我们就可以采用trie树/hash_map等直接来统计每个query出现的次数,然后按出现次数做快速/堆/归并排序就可以了。
方案3:
  与方案1类似,但在做完hash,分成多个文件后,可以交给多个文件来处理,采用分布式的架构来处理(比如MapReduce),最后再进行合并。

4444444444444444444444444444444444444444444444444444444444444444444444444444444

4444444444444444444444444444444444444444444444444444444444444444444444444444444

怎么在海量数据中找出重复次数最多的一个?

    方案1:先做hash,然后求模映射为小文件,求出每个小文件中重复次数最多的一个,并记录重复次数。然后找出上一步求出的数据中重复次数最多的一个就是所求(具体参考前面的题)。

55555555555555555555555555555555555555555555555555555555555555555555555555555

55555555555555555555555555555555555555555555555555555555555555555555555555555

一个文本文件,大约有一万行,每行一个词,要求统计出其中最频繁出现的前10个词,请给出思想,给出时间复杂度分析。

     方案1:这题是考虑时间效率。用trie树统计每个词出现的次数,时间复杂度是O(n*le)(le表示单词的平准长度)。然后是找出出现最频繁的前10个词,可以用堆来实现,前面的题中已经讲到了,时间复杂度是O(n*lg10)。所以总的时间复杂度,是O(n*le)与O(n*lg10)中较大的哪一个。

()()()()()()()()()()()()()()()()()()()()()()()()()()()()(

()()()()()()()()()()()()()()()()()()()()()()()()()()()()(

字典树又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。

(((((((---------------------------------------------------------------------------------------------------------))))))
  Trie树,即字典树,又称单词查找树或键树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
666666666666666666666666666666666666666666666666666666666666666666666666666

666666666666666666666666666666666666666666666666666666666666666666666666666

10. 1000万字符串,其中有些是重复的,需要把重复的全部去掉,保留没有重复的字符串。请怎么设计和实现?

  • 方案1:这题用trie树比较合适,hash_map也行。
  • 方案2:from xjbzju:,1000w的数据规模插入操作完全不现实,以前试过在stl下100w元素插入set中已经慢得不能忍受,觉得基于hash的实现不会比红黑树好太多,使用vector+sort+unique都要可行许多,建议还是先hash成小文件分开处理再综合。
77777777777777777777777777777777777777777777777777777777777777777777777777777

777777777777777777777777777777777777777777777777777777777777777777777777777777777

2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。
    有点像鸽巢原理,整数个数为2^32,也就是,我们可以将这2^32个数,划分为2^8个区域(比如用单个文件代表一个区域),然后将数据分离到不同的区域,然后不同的区域在利用bitmap就可以直接解决了。也就是说只要有足够的磁盘空间,就可以很方便的解决。

8888888888888888888888888888888888888888888888888888888888888888888888888888888888

888888888888888888888888888888888888888888888888888888888888888888888888888888888

5亿个int找它们的中位数。

  1. 思路一:这个例子比上面那个更明显。首先我们将int划分为2^16个区域,然后读取数据统计落到各个区域里的数的个数,之后我们根据统计结果就可以判断中位数落到那个区域,同时知道这个区域中的第几大数刚好是中位数。然后第二次扫描我们只统计落在这个区域中的那些数就可以了。
    实际上,如果不是int是int64,我们可以经过3次这样的划分即可降低到可以接受的程度。即可以先将int64分成2^24个区域,然后确定区域的第几大数,在将该区域分成2^20个子区域,然后确定是子区域的第几大数,然后子区域里的数的个数只有2^20,就可以直接利用direct addr table进行统计了。
  2.   思路二@绿色夹克衫:同样需要做两遍统计,如果数据存在硬盘上,就需要读取2次。
    方法同基数排序有些像,开一个大小为65536的Int数组,第一遍读取,统计Int32的高16位的情况,也就是0-65535,都算作0,65536 - 131071都算作1。就相当于用该数除以65536。Int32 除以 65536的结果不会超过65536种情况,因此开一个长度为65536的数组计数就可以。每读取一个数,数组中对应的计数+1,考虑有负数的情况,需要将结果加32768后,记录在相应的数组内。
    第一遍统计之后,遍历数组,逐个累加统计,看中位数处于哪个区间,比如处于区间k,那么0- k-1的区间里数字的数量sum应该<n/2(2.5亿)。而k+1 - 65535的计数和也<n/2,第二遍统计同上面的方法类似,但这次只统计处于区间k的情况,也就是说(x / 65536) + 32768 = k。统计只统计低16位的情况。并且利用刚才统计的sum,比如sum = 2.49亿,那么现在就是要在低16位里面找100万个数(2.5亿-2.49亿)。这次计数之后,再统计一下,看中位数所处的区间,最后将高位和低位组合一下就是结果了。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值