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

题目描述

输入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)时间删除和插入。

你可能感兴趣的:(剑指offer之面试题30:最小的k个数)