Java 解决剑指offer 最小的k个数

题目描述

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

题目解析

这道题最简单的思路是把数组排序,排序的时间复杂度为O(NlogN)。但是我们有更好得解决方案。

1.时间复杂度为O(n),但需要改变数组。

我们将思路放到快速排序上,在快速排序的Partition中,我们每次选择一个元素,将数组中小于这个元素的值放在这个元素的左边,不小于这个元素的值放在这个元素的右边,并返回这个元素所在的位置。也就是说,我们如果调用Partition方法的返回值是k-1,则数组中0到k-1位置的k个元素即为我们要找的元素。如果不是,则根据返回值的位置继续进行Partition操作。
代码如下:

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList();
        if(input==null||input.length<k||k<=0){
            return list;
        }
        int lo = 0;
        int hi = input.length-1;
        int index = partition(input,lo,hi);
        while(index+1!=k){
            if(index<k){
                lo = index+1;
                index = partition(input,lo,hi);
            }else{
                hi = index-1;
                index = partition(input,lo,hi);
            }
        }
        for(int i=0;i<k;i++){
            list.add(input[i]);
        }
        return list;
    }
    private int partition(int[] input,int lo,int hi){
        if(lo==hi){
            return lo;
        }
        int temp = input[lo];
        int i=lo;
        int j=hi;
        while(i<j){
            while(input[j]>=temp&&j>i){
                j--;
            }
            input[i] = input[j];
            while(input[i]<temp&&i<j){
                i++;
            }
            if(i<j){
                input[j] = input[i];
            }
        }
        input[j] = temp;
        return j;
    }
}

上述方法的明显不足就是我们需要变动输入的数组。

2.时间复杂度为O(Nlogk)的方法,特别适合海量数据。
另一个思路就是我们创建一个容量为k的容器,用来保存最小的k个元素。再遍历数组时,当前元素如果小于容器中的最大值,则将其替换为当前数字,直到遍历结束剩下的元素就是最小的k个元素。容器的选择我们的第一反应应该是大根堆,大根堆使我们每次可以在O(1)的时间复杂度内找到容器中的最大元素,在O(logk)的时间复杂度内完成元素的删除和插入。
下面是基于大根堆的代码:

import java.util.*;
public class Solution {
    private int[] container;
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList();
        if(input==null||input.length<k||k<=0){
            return list;
        }
        container = new int[k+1];
        for(int i=0;i<k;i++){
            container[i+1] = input[i];
            swim(i+1);
        }
        for(int i=k;i<input.length;i++){
            if(input[i]<container[1]){
                container[1] = input[i];
                sink(1);
            }
        }
        for(int i=1;i<k+1;i++){
            list.add(container[i]);
        }
        return list;
    }
    private void sink(int k){
        while((k<<1)<container.length){
            int j = (k<<1);
            if(j+1<container.length&&container[j]<container[j+1]){
                j++;
            }
            if(container[k]>=container[j]){
                return;
            }
            swap(k,j);
            k = j;
        }
    }
    private void swim(int k){
        while(k>1&&container[k>>1]<container[k]){
            swap(k>>1,k);
            k = (k>>1);
        }
    }
    private void swap(int i,int j){
        int t = container[i];
        container[i] = container[j];
        container[j] = t;
    }
}

除了大根堆之外,查找、删除、插入复杂度为O(logk)的容器还有红黑树,当然,红黑树的实现过于复杂,如果我们自己实现一颗红黑树,无论是在面试、考试还是比赛中,都是不现实的。但是Java中为我们提供了一个基于红黑树的集合TreeSet,如果允许的话我们可以直接使用。
下面是基于TreeSet的代码:

import java.util.*;
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> list = new ArrayList();
        TreeSet<Integer> set = new TreeSet();
        if(input==null||input.length<k||k<=0){
            return list;
        }
        for(int i=0;i<k;i++){
            set.add(input[i]);
        }
        for(int i=k;i<input.length;i++){
            if(input[i]<set.last()){
                set.pollLast();
                set.add(input[i]);
            }
        }
        for(int i:set){
            list.add(i);
        }
        return list;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值