剑指offer之面试题30:最小的k个数

来源:互联网 发布:登山鞋知乎 编辑:程序博客网 时间:2024/04/30 10:29

题目描述

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

思路:如果不考虑时间效率的话,很容易想到就是把数组排序,直接用Arrays.sort(int[] a)方法可以对指定的 int 型数组按数字升序进行排序(但有一点注意,这样就会修改输入的数组)。该排序算法是一个经过调优的快速排序法,此算法在许多数据集上提供 n*log(n) 性能。排序之后位于前k个数就是最小的k个数。

Note:如果不想修改数组,可以将数组中的值放进ArrayList中,然后用Collections.sort(List<T> list)该排序算法是一个经过修改的合并排序算法(其中,如果低子列表中的最高元素小于高子列表中的最低元素,则忽略合并)。此算法提供可保证的 n log(n) 性能。 此实现将指定列表转储到一个数组中,并对数组进行排序,在重置数组中相应位置处每个元素的列表上进行迭代。这避免了由于试图原地对链接列表进行排序而产生的 n2 log(n) 性能。 

代码如下:

import java.util.ArrayList;import java.util.Collections;public class Solution {    public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {        ArrayList<Integer> list=new ArrayList<Integer>();        //边界处理        if(input==null||input.length<=0||k>input.length||k<=0)            return list;        //不改变原来的数组,可以把数组值放进list中        for(int i=0;i<input.length;i++){            list.add(input[i]);        }        //排序        Collections.sort(list);        //返回前k个数,即移除后面的数        while(list.size()>k){            list.remove(k);        }        return list;    }    public static void main(String[] args){        int[] input={4,5,1,6,2,7,3,8};        System.out.println(GetLeastNumbers_Solution(input,4).toString());    }}

其他解法:基于Partition函数的O(n)解法
题目中只说找出最小的k个数,没说k的数必须有序,所以基于Partition函数的思想,可以令比第k个数字小的在左边,比其大的在右边,这样返回左面的k个数,即可。

代码如下:

import java.util.ArrayList;public class Solution {    public static ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {        ArrayList<Integer> list=new ArrayList<Integer>();        //边界处理        if(input==null||input.length<=0||k>input.length||k<=0)            return list;        int start=0;        int end=input.length-1;        int index=RandomizedPartition(input,start,end);        //第k个数,下标为k-1,使得input[0..k-2]<=input[k-1]<input[k..end-1]        while(index!=k-1){            if(index>k-1)                index=RandomizedPartition(input,start,index-1);            else                index=RandomizedPartition(input,index+1,end);        }        //把前k个数放进list中        for(int i=0;i<k;i++){            list.add(input[i]);        }        return list;    }    public static int RandomizedPartition(int[] input, int start, int end) {        int temp=(int)(Math.random()*(end - start + 1)) + start;        swap(input,temp,end);        int i=start-1;        for(int j=start;j<end;j++){            if(input[j]<=input[end]){                i++;                swap(input,i,j);            }        }        swap(input,i+1,end);        return i+1;    }    public static void swap(int[] array, int k, int end) {        int temp=array[k];        array[k]=array[end];        array[end]=temp;            }    public static void main(String[] args){        int[] input={4,5,1,6,2,7,3,8};        System.out.println(GetLeastNumbers_Solution(input,4).toString());    }}

如果不能修改输入数组,怎么办?
如果不修改输入数组,且又把前k个数保存,只能创建一个k大小的容器用来存放最小的k个数。然后怎么做?
当容器不满时(数字小于k),直接放进容器里。
当容器满时,不能直接放进去,想到可以利用操作系统中页面置换的思想,把已在容器中的不满足最小k个数的数字替换出去,首先替换出去的应该是k个数中最大的(可以排除),当然如果待插入的数字比容器中最大的数字还大,无需替换,直接遍历下一个。

总之,当容器满时需要做3件事:1容器中找到最大数;2有可能删除最大数;3在2的基础上向容器中加入一个新的数。想到最大堆满足在O(1)时间找到最大数,且在O(logk)时间删除和插入。

0 0