• 剑指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)

  • 相关阅读:
    【python】python中的定义类属性和对像属性
    【Python】Python—判断变量的基本类型
    【python】Python中给List添加元素的4种方法分享
    【python】python各种类型转换-int,str,char,float,ord,hex,oct等
    【python】python 中的三元表达式(三目运算符)
    【python】 sort、sorted高级排序技巧
    【SQLAlchemy】SQLAlchemy技术文档(中文版)(中)
    【SQLAlchemy】SQLAlchemy技术文档(中文版)(上)
    【其他】VS提示不一致的行尾
    UML 之 用例图
  • 原文地址:https://www.cnblogs.com/hi3254014978/p/12589200.html
Copyright © 2020-2023  润新知