100亿个整数,内存足够,如何找到中位数?内存不足,如何找到中位数?

首先必须清楚中位数的定义:

中位数(又称中值,英语:Median),统计学中的专有名词,代表一个样本、种群或概率分布中的一个数值,其可将数值集合划分为相等的上下两部分。对于有限的数集,可以通过把所有观察值高低排序后找出正中间的一个作为中位数。如果观察值有偶数个,通常取最中间的两个数值的平均数作为中位数。

然后这个题答案:

内存足够的情况: 可以使⽤用类似quick sort的思想进行,均摊复杂度为O(n),算法思想如下: 
• 随机选取一个元素,将比它小的元素放在它左边,比它大的元素放在右边 
• 如果它恰好在中位数的位置,那么它就是中位数,可以直接返回 
• 如果小于它的数超过一半,那么中位数一定在左半边,递归到左边处理 
• 否则,中位数一定在右半边,根据左半边的元素个数计算出中位数是右半边的第几大,然后递归 到右半边处理 
内存不⾜足的情况: 
方法⼀:⼆分法 
思路:一个重要的线索是,这些数都是整数。整数就有范围了,32位系统中就是[-2^32, 2^32- 1], 有了范围我们就可以对这个范围进行二分,然后找有多少个数⼩于Mid,多少数大于mid,然后递归, 和基于quicksort思想的第k大⽅方法类似 
方法二:分桶法 思路:化大为小,把所有数划分到各个小区间,把每个数映射到对应的区间⾥里,对每个区间中数的 个数进行计数,数一遍各个区间,看看中位数落在哪个区间,若够小,使⽤用基于内存的算法,否则 继续划分

然后讲解下快速排序以及基于快排思想的找前k个最大数:

  • 快速排序是对冒泡排序的改进。
    • 快速排序是C.R.A.Hoare于1962年提出的一种划分交换排序,它采用一种分治(Divide-and-ConquerMethod)的方法
    • 快速排序的思想:
      • 在数组中找到一个基准数(pivot)
      • 分区,将数组中比基准数大的放到它的右边,比基准数小的放到它的左边
      • 继续对左右区间重复第二步,直到各个区间只有一个数,这时候,数组也就有序了。
    • 代码:
[cpp]  view plain  copy
 print ?
  1. int Partition(vector<int> &v, int head, int rear){  
  2.     int key = v[head];  
  3.     while (head < rear){  
  4.         while (v[rear] <= key && head < rear){  
  5.             --rear;  
  6.         }  
  7.         swap(v[head], v[rear]);  
  8.         while (v[head] >= key && head < rear){  
  9.             ++head;  
  10.         }  
  11.         swap(v[rear], v[head]);  
  12.           
  13.     }  
  14.     v[head] = key;  
  15.     ++my_count;  
  16.     return head;  
  17. }  
  18.   
  19. void QuickSort(vector<int> &v, int head, int rear){  
  20.     int pivot = -1;  
  21.     if (head < rear){  
  22.         pivot = Partition(v, head, rear);  
  23.         QuickSort(v, head, pivot - 1);  
  24.         QuickSort(v, pivot + 1, rear);  
  25.     }  
  26. }  

    • Note: Partition函数中 v[rear] <= key 以及 v[head] >= key 表达式必须包含等于的判断,否则当数组两头的数相等时将会造成死循环  例如 {5,2,6,2,9,10,5}
    • Partition函数:最慢情况下快速排序会进行 size()- 1 次 Partition函数,而每次调用,Partition函数会选择一个基准数,例如v[head]或者v[rear],或者任意一个数组中的数。之后分别从两头扫描,碰到比基准数大或者小的数就与上一个head或rear交换,或者直到head大于等于rear时,此次循环结束。
    • QuickSort函数:该函数采用递归的方法,每次调用一次Partition函数,得到一个基准数的索引和相对基准数有序的数列,之后将该基准数左边的数组和右边的数组分别调用QuickSort函数,也就是它本身。直到数组中只有一个数时,这条递归序列便结束。
  • 基于快速排序的查找前k个最大数
    • 由上可知,快排的思想是每次找到一个基准数,将数组排列成基准数左边的每个数都比基准数大,右边的每个数都比基准数小的序列。
    • 通过这个思想,我们可以稍微修改QuickSort函数,使它变成QuickSearch函数,使之拥有快速查找前k个最大的数。
[cpp]  view plain  copy
 print ?
  1. int QuickSearch(vector<int> &v, int head, int rear, int k){  
  2.     int pivot = -1, len = 0;  
  3.     if (head < rear){  
  4.         pivot = Partition(v, head, rear);  
  5.         len = pivot - head + 1;  
  6.         if (len < k){  
  7.             pivot = QuickSearch(v, pivot + 1, rear, k - len);  
  8.         }  
  9.         else if (len > k){  
  10.             pivot = QuickSearch(v, head, pivot - 1, k);  
  11.         }  
  12.     }  
  13.     return pivot;  
  14. }  

  • 上图中,我们可以发现,函数参数多了一个k,这个值是表示要获取前k个最大数。
  • 函数中多了一些逻辑,每次执行完Partition函数,根据获取的基准值索引,计算基准值左边数组的长度。
  • len < k,则说明,在基准值左边的数组中已经有了len个最大数,此时,我们只需在基准值右边的数组再找k - len个最大数即可,所以只要再次调用QuickSearch函数,并传入k-len参数以及基准值右边的数组索引。
  • len > k,则说明,此时基准值左边已经有了len个最大值,然而len大于k,我们并不需要那么多的最大值,所以再次调用QuickSearch函数,传入基准值左边的数组索引,以及k,获得这个长度len的最大数集的子集

你可能感兴趣的:(面试笔试题)