剑指offer29_最小的k个数

题目描述

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

解题思路参考:https://www.cnblogs.com/edisonchou/p/4799678.html

1 需要修改数据源的O(n)解法

  基于快速排序中的Partition函数来解决这个问题。如果基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组中左边的k个数字就是最小的k个数字(这k个数字不一定是排序的)。

  But,采用这种思路是有限制的。我们需要修改输入的数组,因为函数Partition会交换数组中数字的顺序。

具体代码如下:

//与上一道找中位数的题目写法类似,就是把中位数array.length>>1替换为k-1而已
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> leastNumbers = new ArrayList<Integer>();
        while(input==null || k<=0 || k>input.length)
            return leastNumbers;
        int start=0;
        int end=input.length-1;
        int index=partition(input,start,end);
        while(index!=k-1){
            if(index<k-1){
                start=index+1;
                index=partition(input,start,end);
            }else{
                end=index-1;
                index=partition(input,start,end);
            }
        }
        //index=k-1的时候,直接将前k位赋值给链表即可
        for(int i=0;i<k;i++){
            leastNumbers.add(input[i]);
        }
        return leastNumbers;
    }
      
    private int partition(int[] arr, int start,int end){
        int pivotKey=arr[start];
        while(start<end){
            while(start<end && arr[end]>=pivotKey)
                end--;
            swap(arr,start,end);
            while(start<end && arr[start]<=pivotKey)
                start++;
            swap(arr,start,end);
        }
        return start;
    }
      
    private void swap(int[] arr, int i,int j){
        int temp=arr[i];
        arr[i]=arr[j];
        arr[j]=temp;
    }
}

2 适合处理海量数据的O(nlogk)解法

  可以先创建一个大小为k的数据容器来存储最小的k个数字,接下来我们每次从输入的n个整数中读入一个数。

  如果容器中已有的数字少于k个,则直接把这次读入的整数放入容器之中

  如果容器中已有k个数字了,也就是容器已满,此时我们不能再插入新的数字而只能替换已有的数字

  找出这已有的k个数中的最大值,然后拿这次待插入的整数和最大值进行比较。如果待插入的值比当前已有的最大值小,则用这个数替换当前已有的最大值;如果待插入的值比当前已有的最大值还要大,那么这个数不可能是最小的k个整数之一,于是我们可以抛弃这个整数。

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

此解法虽然要慢一点,但它有两个明显的优点:

一是没有修改输入的数据(代码中的变量data)。我们每次只是从data中读入数字,所有的写操作都是在容器leastNumbers中进行的。

二是该算法适合海量数据的输入(包括百度在内的多家公司非常喜欢与海量输入数据相关的问题)。

假设题目是要求从海量的数据中找出最小的k个数字,由于内存的大小是有限的,有可能不能把这些海量的数据一次性全部载入内存。这个时候,我们可以从辅助存储空间(比如硬盘)中每次读入一个数字,根据GetLeastNumbers的方式判断是不是需要放入容器leastNumbers即可。这种思路只要求内存能够容纳leastNumbers即可,因此它最适合的情形就是n很大并且k较小的问题

借助于java中的PriorityQueue,我们可以很简单的实现:

import java.util.PriorityQueue;
import java.util.Comparator;
import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> reslist=new ArrayList<Integer>();
        if(input==null||input.length==0||k<=0||k>input.length){
            return reslist;
        }
        PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(k,new Comparator<Integer>(){
            public int compare(Integer a,Integer b){
                return b-a;
            }
        });
        for(int i=0;i<input.length;i++){
            if(i<k){
                maxHeap.offer(input[i]);
            }
            else{
                int temp=maxHeap.poll();
                if(temp>input[i]){
                    maxHeap.offer(input[i]);
                }
                else{
                    maxHeap.offer(temp);
                }
            }
        }
        for(int i=0;i<k;i++){
            reslist.add(maxHeap.poll());
        }        
        return reslist;
    }
}

如果是底层java实现的话,非常复杂,需要建立最大堆,并每次调整。

最大堆调整的思路如下:

 序列对应一个完全二叉树;从最后一个分支结点(n/2)开始,到根(1)为止,依次对每个分支结点进行调整(下沉),以便形成以每个分支结点为根的堆,当最后对树根结点进行调整后,整个树就变成了一个堆。 
  时间:O(n) 
对如图的序列,要使其成为堆,我们从最后一个分支结点(10/2),其值为72开始,依次对每个分支节点53,18,36 45进行调整(下沉). 

<转载>最大堆的四种操作(附JAVA代码)

<转载>最大堆的四种操作(附JAVA代码)

<转载>最大堆的四种操作(附JAVA代码)

先写出代码中的最大堆的调整方法及注释:
    private void adjustHeap(int[] arr,int start,int end){
        int temp=arr[start];
        int child=start*2+1;
        while(child<=end){
            if(child+1<=end && arr[child+1]>arr[child])
                child++;//遍历一遍,如果右边节点比左边大,就child++到了右边
            if(arr[child]<temp)
                break;//arr[start]>arr[child]那么后面也无需调整,直接break
            arr[start]=arr[child];//前一句没有跳出的话,就说明是arr[start]<=arr[child],那就child上浮
            start=child;
            child=child*2+1;//start与child的递增方式

        }
        arr[start]=temp;//在所有必要的上浮结束后,最后一轮的start点被赋值为需要下沉的初始start点
    }

总的最大堆方法求K最小代码如下:

import java.util.ArrayList;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> leastNumbers = new ArrayList<Integer>();

       //特殊情况处理
        while(input==null || k<=0 || k>input.length)
            return leastNumbers;
        int[] numbers=new int[k];  //新建一个大小为k的数组,用于放最小的k个数
        for(int i=0;i<k;i++)
            numbers[i]=input[i];//先放入前k个数

       //将由底及上的将前k个值构造成最大堆形式,这时numbers[0]存储前k里最大值
        for(int i=k/2-1;i>=0;i--){
            adjustHeap(numbers,i,k-1);
        }

        for(int i=k;i<input.length;i++){
            if(input[i]<numbers[0]){ //存在更小的数字时,把numbers[0]替换并调整堆
                numbers[0]=input[i];
                adjustHeap(numbers,0,k-1);//重新调整最大堆
            }
        }
        for(int n:numbers)
            leastNumbers.add(n);
        return leastNumbers;
    }
      
    //最大堆的调整方法
    private void adjustHeap(int[] arr,int start,int end){
        int temp=arr[start];
        int child=start*2+1;
        while(child<=end){
            if(child+1<=end && arr[child+1]>arr[child])
                child++;
            if(arr[child]<temp)
                break;
            arr[start]=arr[child];
            start=child;
            child=child*2+1;
        }
        arr[start]=temp;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值