剑指offer 29.最小的K个数

 29.最小的K个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。

思路一:

1.. 建立最大堆,对数组前 k 个元素建立最大堆,对 k 后面的每个元素,与堆顶元素进行大小比较,如果小于堆顶元素,则用当前元素替换为堆顶元素,并进行一次向下过滤,如果不小于堆顶元素,则直接跳过。

始终保证堆中的元素是小的 k 个

 1 import java.util.ArrayList;
 2 public class Solution {
 3     
 4     public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
 5 
 6         // 建立最大堆
 7         ArrayList<Integer> list = new ArrayList<Integer>(k);
 8 
 9         if (input == null || input.length < k) {
10             return list;
11         }
12         // 建堆
13         int n = k;
14         for (int i = (n / 2 - 1); i >= 0; i--) {
15             downAdj(input, i, n);
16         }
17 
18 
19         // 每当读入一个元素,与堆顶元素比较大小,
20         for (int i = k; i < input.length; i++) {
21             // 如果小于大堆顶元素,则用当前数替换堆顶元素,进行一次向下过滤
22             // 如果小于的话,直接丢弃这个数
23             if (input[i] < input[0]) {
24                 input[0] = input[i];
25                 downAdj(input, 0, n);
26             }
27 
28         }
29 
30         // 最后对这个堆进行堆排序,输出
31         for (int i = n - 1; i >= 0; i--) {
32             int temp = input[0];
33             input[0] = input[i];
34             input[i] = temp;
35             downAdj(input, 0, i);
36         }
37 
38         // 把 k 个数放到集合中
39         for(int i = 0; i < n; i++){
40             list.add(input[i]);
41         }
42         return list;
43     }
44 
45     // 向下过滤
46     public void downAdj(int[] A, int p, int n) {
47         int parent, child = p;
48         int x = A[p];
49         for (parent = p; 2 * parent < n; parent = child) {
50             child = 2 * parent;
51             if (child + 1 < n && A[child + 1] > A[child]) {
52                 child++;
53             }
54             // 与最大的孩子结点比较,如果父节点大,说明找到了位置,
55             if (x >= A[child]) {
56                 break;
57             }
58             // 否则让最大的孩子上位
59             else {
60                 A[parent] = A[child];
61             }
62 
63         }
64         A[parent] = x;
65     }
66 }

使用PriorityQueue代替手动建堆过程

 1 class Solution {
 2     // 使用最小堆,每次从堆中取一个元素
 3     public int[] getLeastNumbers(int[] arr, int k) {
 4         PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(K+1);
 5         int[] res = new int[k];
 6         int len = arr.length;
 7         for(int i = 0; i < len; i++){
 8             minHeap.offer(arr[i]);
 9         }
10         for(int i = 0; i < k; i++){
11             res[i] = minHeap.poll();
12         }
13         return res;
14     }
15 }

leetcode运行时间为19ms, 空间为39.8MB

复杂度分析:

时间复杂度:建堆过程花费的时间为O(nlog(k+1)), 取前k 个元素花费的时间为O(klogk)

空间复杂度:堆的大小即为空间复杂度,所以空间复杂度为O(k+1)

思路二: 快速排序

利用快速排序,经过若干轮轮快排后,如果主元位置刚好为k-1, 那么前半段元素即使数组中最小的k个元素

 1 class Solution {
 2     // 利用快速排序的过程,经过若干轮轮快排后,如果主元位置刚好为k-1, 那么前半段元素即使数组中最小的k个元素
 3     public int[] getLeastNumbers(int[] arr, int k) {
 4 
 5        // arr进行若干轮快排
 6        quickSort(arr, 0, arr.length-1, k);
 7 
 8        // 返回前k个元素的范围数组 
 9        return Arrays.copyOfRange(arr, 0, k);
10     }
11 
12     // 快排
13     public void quickSort(int[] arr, int left, int right, int k){
14         // 如果left < right继续快排
15         if(left < right){
16             // 对[left, right]区间的元素进行一轮快排,获取到快排后的主元位置
17             int pivotIndex = partition(arr, left, right);
18             if(pivotIndex == k-1){
19                 return;             // 如果主元位置刚好是k-1, 直接返回
20             }
21             // 否则,继续递归快排左右子区间
22             quickSort(arr, left, pivotIndex - 1, k);
23             quickSort(arr, pivotIndex + 1, right, k);
24         }
25     }
26 
27     // 一次快排操作
28     public int partition(int[] arr, int left, int right){
29         // 选定第一个元素作为主元
30         int pivot = arr[left];
31         // 当left < right时一直循环
32         while(left < right){
33             while(left < right && arr[right] >= pivot){
34                 right--;
35             }
36             arr[left] = arr[right];
37             while(left < right && arr[left] <= pivot){
38                 left++;
39             }
40             arr[right] = arr[left];
41         }
42         arr[left] = pivot;
43         return left;
44     }
45 }

leetcode运行时间为9ms, 空间为40.3MB,可以看到比思路一快很多

复杂度分析:

时间复杂度:若干次快排,复杂度为O(n), 具体不会证明,参考:具体证明可以参考《算法导论》第 9 章第 2 小节。

空间复杂度:递归栈的深度最小为O(logn), 最大为O(n)

原文地址:https://www.cnblogs.com/hi3254014978/p/12589200.html