JZ40 最小的 K 个数

题目描述

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

示例1

输入

[4,5,1,6,2,7,3,8],4

返回值

[1,2,3,4]

思路: 这道题是典型的TopK问题,最简单的思路就是把输入的 n 个整数排序,排序之后位于最前面的 k 个数就是最小的 k 个数,这种思路的时间复杂度是 O(nlogn)

使用快排思想

对数组[l, r]一次快排partition过程可得到,[l, p), p, [p+1, r)三个区间,[l,p)为小于等于p的值
[p+1,r)为大于等于p的值。
然后再判断p

  1. 如果[l,p), p,也就是p+1个元素(因为下标从0开始),如果p+1 == k, 找到答案
  2. 如果p+1 < k, 说明答案在[p+1, r)区间内,
  3. 3, 如果p+1 > k , 说明答案在[l, p)内

实现代码如下:

public ArrayList<Integer> GetLeastNumbers(int[] arr, int k){

    ArrayList<Ingeger> res = new ArrayList<>();
    if(arr == null)
        return res;
    int start = 0,end = arr.length - 1;
    
    while(start < end){
        int index = partition(arr,start,end);
        if(index +1 == k ){ //找到前k小的数了,跳出循环
             break;
        }
        else if(index + 1 < k){//从右边继续寻找
            start = index + 1;
        }
        else if(index + 1 > k){//从左边继续寻找
            end = index - 1;
            
        }
    }
    for(int i = 0;i < k;i++){
        res.add(arr[i]);
    }
    return res;
}

//快排partition操作,返回的索引
public static int partition(int arr,int start,int end){
    int key = arr[end];//设置基准值
    while(start < end){
        while(start < end && arr[start] <= k)
            start++;
        arr[end] = arr[start];
        while(start < end && arr[end] >= k)
            end--;
        arr[start] = arr[end];
    }
    arr[start] = key;
    return start;

}

 数组中第 k 大的数 、数组中超过一半的数字

注意:

该种解法会修改输入的数组,因为函数会调整数组中的数字的顺序。不能修改输入的数组,可以考虑用以下方法(最大堆):

我们可以先创建一个大小为 k 的数据容器来存储最小的 k 个数字,接下来每次从输入的 n 个整数中读入一个数。如果容器中已有的数字少于 k 个,则直接从把这次读入的整数放入容器中;如果容器中已有 k 个数字了,也就是容器已满,此时我们不能再插入新的数字而只能替换已有的数字了,找出这已有的 k 个数字中的最大值,然后拿这次插入的整数和最大值进行比较,如果待插入的值比当前已有的最大值小,则用这个数替换当前已有的最大值;如果待插入的值比当前已有的最大值还要大,那么这个数就是最小的 k 个整数之一了,于是我们直接扔掉这个数。

因此,当容器满了之后,我们需要做3件事情:一是在 k 个整数中找到最大值;二是有可能在这个容器中删除最大数;三是有可能要插入一个新的数字。如果用一棵二叉树来实现这种数据容器,那么我们能在 O(logk)时间内实现这三步操作。因此 ,对于 n 个输入数字而言,总的时间效率就是 O(nlogk).

由于每次都需要找到 k 个整数中的最大数字,我们可以选择用最大堆,最大堆中,根节点的值总是大于它的子树中任意节点的值。于是每次可以在 O(1)时间内得到已有的 k 个数字中的最大值,但需要 O(logk)时间完成删除及插入操作。

代码如下:

import java.util.ArrayList;
import java.util.PriorityQueue;
import java.util.Comparator;
public class Solution {
   public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
       ArrayList<Integer> result = new ArrayList<Integer>();
       int length = input.length;
       if(k > length || k == 0){
           return result;
       }
        PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(k, new Comparator<Integer>() {
  
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2.compareTo(o1);
            }
        });
        for (int i = 0; i < length; i++) {
            if (maxHeap.size() != k) {
                maxHeap.offer(input[i]);
            } else if (maxHeap.peek() > input[i]) {
                Integer temp = maxHeap.poll();
                temp = null;
                maxHeap.offer(input[i]);
            }
        }
        for (Integer integer : maxHeap) {
            result.add(integer);
        }
        return result;
    }
}

不用java中 PriorityQueue 中的集合,自己实现一个最大堆的代码:

public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list=new ArrayList<Integer>();
        //检查输入的特殊情况,这道题花了好长时间,就是因为这个边界条件       
        if(input==null || input.length<=0 || input.length<k){
            return list;
        }        
        //构建最大堆
        int len = input.length;
        for(int i=k/2-1; i>=0; i--){
            adjustMaxHeapSort(input,i,k);
        }
        //从第k个元素开始分别与最大堆的最大值做比较,如果比最大值小,则替换并调整堆。
        //最终堆里的就是最小的K个数。
        int tmp;

        for(int i=k; i<len; i++){
            if(input[i]<input[0]){
                tmp=input[0];
                input[0]=input[i];
                input[i]=tmp;
                adjustMaxHeapSort(input,0,k);
            }
        }
        for(int j=0; j<k; j++){
            list.add(input[j]);
        }
        return list;
    }

 public void adjustMaxHeapSort(int[] input, int i, int length){
        int temp = input[i];
        for(int k = 2*i+1;k < length;k=2*k+1){
            if(k+1 < length && input[k+1] > input[k])
                k++;
            if(input[k] > temp){
                input[i] = input[k];
                i = k;
            }
           
        }
        input[i] = temp;
    }
        

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值