40. 最小的K个数
1 题目描述
输入整数数组 arr
,找出其中最小的 k
个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
2 题目分析
最小的k个数,直观思路就是先排序然后找出前k个,这种解法当然不行,下面介绍两种海量数据中的topK问题思路:
1. 用大顶堆:首先将前k个元素加入大顶堆中,然后从第k+1个开始将当前元素和堆顶元素比较,把较小者加入堆中(Java中用优先队列实现,如果不需要自己手动写堆的话)
2. 快排partition思想:以基准pivot将数组划分为两部分,左边一部分小于等于pivot,右边一部分大于pivot;这里与快排不同的是快排需要对左边和右边两部分都要处理,
这里只需要处理一边即可。具体如下:
定义一个partition(arr, start, end, k)表示划分数组arr的[start,end]部分,使前k个小的数在数组的左侧,假设划分返回的下标为pos,即pivot是数组中第
pos-l+1小的数,那么会有三种情况:
* 如果pos-l+1==k,表示pivot就是第k小的数,直接返回即可。
* 若果pos-l+1<k,表示第k小的数在pivot的右侧,因此递归调用partition(arr, pos + 1, end, k-(pos - l + 1))
* 如果post-l+1>k,表示第k小的数在pivot的左侧,递归调用partition(arr, start, pos - 1, k)。
递归入口:partition(arr, 0, arr.length - 1 ,k)
> 注意:pivot的选择常见的有三种1. 选择l作为基准;2. 选择r作为基准;3. 选择一个随机索引与l或者r交换,将l或者r作为基准。
> 本题采用基于随机的划分,与l交换
3 代码
-
大顶堆
public int[] getLeastNumbers(int[] arr, int k) { int[] ans = new int[k]; if (k == 0) return ans; // 创建大顶堆 PriorityQueue<Integer> pq = new PriorityQueue<>((x, y) ->(y - x)); // 先将前k个加入大顶堆中 for (int i = 0; i < k; i++) { pq.offer(arr[i]); } // 从第k+1个开始比较当前元素和堆顶元素 int n = arr.length; for (int i = k; i < n; i++) { if (pq.peek() > arr[i]) { pq.poll(); pq.offer(arr[i]); } } for (int i = 0; i < k; i++) { ans[i] = pq.poll(); } return ans; }
-
随机快速选择
public int[] getLeastNumbers1(int[] arr, int k) { // 递归入口 randomSelected(arr, 0, arr.length - 1, k); // 创建大小为k的数组存储前k个元素 int[] ans = new int[k]; for (int i = 0; i < k; i++) { ans[i] = arr[i]; } return ans; } private void randomSelected(int[] arr, int start, int end, int k) { // 递归出口 if (start >= end) return; int pos = randomPartition(arr, start, end); int num = pos - start + 1; // 表示第num小的元素 if (k == num) return; else if (k < num) { randomSelected(arr, start, pos - 1, k); } else { randomSelected(arr, pos + 1, end, k - num); } } private int randomPartition(int[] arr, int start, int end) { // 生成一个随机索引号 int i = new Random().nextInt(end - start + 1) + start; swap(arr, end, i); return parition(arr, start, end); } private int parition(int[] arr, int start, int end) { // 取最右边元素为基准 int pivot = arr[end]; int index = start - 1; for (int i = start; i <= end - 1; i++) { if (arr[i] <= pivot) { index++; swap(arr, index, i); } } swap(arr, end, index + 1); return index + 1; } public void swap(int[] arr, int a, int b) { int temp = arr[a]; arr[a] = arr[b]; arr[b] = temp; }