《剑指offer》面试题40:最小的k个数

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


思路:

1、排序

把输入的n个整数排序,然后取前k个数;

时间复杂度:O(nlogn)

2、Partition

通过partition找到第k大的数,它的左边就是前k小的数;

时间复杂度:O(n)

3、最大堆

构建k个整数的最大堆数据结构,然后将剩余n-k个整数依次与堆顶比较,大则抛弃,小则删除堆顶并插入,最后的最大堆就是最小的k个整数;

堆是基于二叉树来实现的,因此插入和删除操作都在O(logk)时间内完成。在代码中可以通过STL中的容器来实现,如set,multiset,priority_queue等,都是基于红黑树实现的,所以是排序的。

时间复杂度:O(nlogk)

完整java代码参考如下:

package chapter5;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;

public class P209_KLeastNumbers {

    public static int partition(int[] arr,int l,int r){
        if(l>=r) return -1;

        int less=l-1;
        int more=r;
        int index=l;
        while (index<more){
            if(arr[index]<arr[r]){
                swap(arr,++less,index++);
            }else if(arr[index]>arr[r]){
                swap(arr,--more,index);
            }else {
                index++;
            }
        }
        swap(arr,more,r);
        return more;
    }

    public static void swap(int[] a,int i,int j){
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }

    public static int[] findKth(int [] arr,int k){
        if(arr==null||arr.length<k||k<0) return new int[]{-1};
        int start=0;
        int end=arr.length-1;
        int index=partition(arr,start,end);
        while (index!=k-1){
            if(index>k-1){
                end=index-1;
                index=partition(arr,start,end);
            }else{
                start=index+1;
                index=partition(arr,start,end);
            }
        }
        int[] res=new int[k];
        for(int i=0;i<k;i++){
            res[i]=arr[i];
        }
        return res;
    }
    public static int findKthLargest(int[] nums,int k){
        int lo=0,hi=nums.length-1;
        int index=partition(nums,lo,hi);
        while(index!=k-1){
            if(index>k-1){
                hi=index-1;
                index=partition(nums,lo,hi);
            }else {
                lo=index+1;
                index=partition(nums,lo,hi);
            }
        }
        return nums[k-1];
    }

    public static class MyComparator implements Comparator<Integer>{
        @Override
        public int compare(Integer o1, Integer o2) {
            return o2-o1;//将先前Integer中的自然排序(从小到大)反过来,实现从大到小;
        }
    }
    //解法二:不改变原始数组,使用优先队列,时间复杂度O(nlogk),适合海量数据
    public static int[] findKth2(int [] arr, int k) {
        if(arr == null || k > arr.length || k<=0) return null;
        //优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素)
        //元素大小的评判可以通过元素本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator,类似于C++的仿函数)
        PriorityQueue<Integer> maxQueue = new PriorityQueue(new MyComparator());
        //PriorityQueue的peek()和element操作是常数时间,add(), offer(), 无参数的remove()以及poll()方法的时间复杂度都是log(N)。
        for(int i =0;i<arr.length;i++){
            if(maxQueue.size() != k ){
                maxQueue.add(arr[i]);
            }else if(arr[i]<maxQueue.peek()){
                maxQueue.poll();//必须先去除队列头部的数据,以保证队列长度
                maxQueue.add(arr[i]);
            }
        }
        int[] res=new int[k];
        int i=0;
        for(Integer num : maxQueue){//遍历
            res[i++]=num;
        }
        return res;
    }

    public static void main(String[] args){
        int k=4;
        int[] arr={4,5,1,6,2,7,3,8};
        int[] res=findKth(arr,k);
        int[] res2=findKth2(arr,k);
        for(int num:res){
            System.out.print(num+" ");
        }
        System.out.println();
        for(int num:res2){
            System.out.print(num+" ");
        }
    }
}

与快排相关的java实现如下:

//最重要就是partition函数的实现

public class quiksort {
    public static int partition(int[] nums,int lo,int hi){
        //将数组切分为a[lo...i-1],a[i],a[i+1...hi]
        int i=lo,j=hi+1;//左右扫面指针
        int v=nums[lo];//切分元素
        while (true){
            //扫描左右,检查扫描是否结束并交换元素
            while (less(nums[++i],v)) if(i>=hi) break;
            while (less(v,nums[--j])) if(j<=lo) break;
            if(i>=j) break;
            swap(nums,i,j);
        }
        swap(nums,lo,j);//将v=nums[j]放入正确的位置
        return j;//a[lo...j-1]<=a[j]<=a[j+1...hi]达成
    }
    public static boolean less(int a,int b){
        if(a<b) return true;
        else return false;
    }
    public static void swap(int[] a,int i,int j){
        int temp=a[i];
        a[i]=a[j];
        a[j]=temp;
    }
    public static int[] findKth(int[] nums,int[] out,int k){
        int lo=0,hi=nums.length-1;
        int index=partition(nums,lo,hi);
        while(index!=k-1){
            if(index>k-1){
                hi=index-1;
                index=partition(nums,lo,hi);
            }else {
                lo=index+1;
                index=partition(nums,lo,hi);
            }
        }
        for(int i=0;i<k;i++){
            out[i]=nums[i];
        }
        return out;
    }
    public static int findKthLargest(int[] nums,int k){
        int lo=0,hi=nums.length-1;
        int index=partition(nums,lo,hi);
        while(index!=k-1){
            if(index>k-1){
                hi=index-1;
                index=partition(nums,lo,hi);
            }else {
                lo=index+1;
                index=partition(nums,lo,hi);
            }
        }
        return nums[k-1];
    }

    public static void sort(int[] nums){
        sort(nums,0,nums.length-1);
    }
    public static void sort(int[] nums,int lo,int hi){
        if(lo>=hi) return;
        int index=partition(nums,lo,hi);//用partition函数切分
        sort(nums,lo,index-1);//将左半部分a[lo...j-1]排序
        sort(nums,index+1,hi);//将右半部分a[j+1...hi]排序
    }

    public static void main(String[] args){
        int k=4;
        int[] n={4,5,1,6,2,7,3,8};
        int[] o=new int[k];
//        findKth(n,o,k);
//        System.out.println(findKthLargest(n,k));
        sort(n);
        for(int num:n){
            System.out.println(num);
        }
    }
}

利用java的PriorityQueue(最大堆方法)来解决,代码如下:

package Sort;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.PriorityQueue;

public class getleastnumber {
    //解法二:不改变原始数组,使用优先队列,时间复杂度O(nlogk),适合海量数据
    public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> result = new ArrayList<>();
        if(input == null || k > input.length || k<=0) return result;
        //优先队列的作用是能保证每次取出的元素都是队列中权值最小的(Java的优先队列每次取最小元素,C++的优先队列每次取最大元素)
        //元素大小的评判可以通过元素本身的自然顺序(natural ordering),也可以通过构造时传入的比较器(Comparator,类似于C++的仿函数)
        PriorityQueue<Integer> maxQueue = new PriorityQueue(k,new Comparator<Integer>(){//使用指定的初始容量创建一个 PriorityQueue,并根据指定的比较器对元素进行排序。
            @Override
            public int compare(Integer o1,Integer o2){
                return o2.compareTo(o1);//将先前Integer中的自然排序(从小到大)反过来,实现从大到小;
            }
        });
        //PriorityQueue的peek()和element操作是常数时间,add(), offer(), 无参数的remove()以及poll()方法的时间复杂度都是log(N)。
        for(int i =0;i<input.length;i++){
            if(maxQueue.size() != k ){
                maxQueue.offer(input[i]);
            }else if(maxQueue.peek() > input[i]){
                Integer temp = maxQueue.poll();//必须先去除队列头部的数据,以保证队列长度
                temp = null;
                maxQueue.offer(input[i]);
            }
        }
        for(Integer i : maxQueue){
            result.add(i);
        }
        return result;
    }
    public static void main(String[] args){
        ArrayList<Integer> result = new ArrayList<>();
        int[] nums={4,5,1,6,2,7,3,8};
        int k=4;
        result=GetLeastNumbers_Solution(nums,k);
        for(int num:result){
            System.out.println(num);
        }
    }

}

基于排序,参考代码如下:

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        vector<int> n;
        if(k > input.size()) return n;
        sort(input.begin(), input.end());
        while(k){
            n.insert(n.begin(), input[k-1]);
            k--;
        }
        return n;
    }
};

基于Partition,参考代码如下:

int Partition(int* numbers,int start,int end){
    int key=numbers[start];
    int i=start;
    int j=end;
    while(i<j){
        while(i<j && numbers[j]>=key)
            --j;
        if(i<j) numbers[i++]=numbers[j];
 
        while(i<j && numbers[i]<=key)
            ++i;
        if(i<j) numbers[j--]=numbers[i];
    }
    numbers[i]=key;
    return i;
}
 
void GetLeastNumbers(int* input,int n,int* output,int k){
    if(input==NULL || output==NULL || k>n || n<=0 || k<=0)
        return;
    int start=0;
    int end=n-1;
    int index=Partition(input,start,end);
    while(index!=k-1){
        if(index>k-1){
            end=index-1;
            index=Partition(input,start,end);
        }
        else{
            start=index+1;
            index=Partition(input,start,end);
        }
    }
 
    for(int i=0;i<k;i++)
        output[i]=input[i];
}

基于最大堆,参考代码如下:

typedef multiset<int,greater<int> > inSet;
typedef multiset<int,greater<int> >::iterator setIterator;
 
void GetLeastNumbers_1(const vector<int> &data,inSet &leastNumbers,unsigned int k){
    leastNumbers.clear();
 
    if(k<1 || data.size()<k)
        return;
 
    vector<int>::const_iterator it=data.begin();
    for(;it!=data.end();it++){
        if(leastNumbers.size()<k)
            leastNumbers.insert(*it);
        else{
            setIterator iterGreatest=leastNumbers.begin();
            if(*it<*iterGreatest){
                leastNumbers.erase(iterGreatest);
                leastNumbers.insert(*it);
            }
        }
    }
}
测试用例:

a.功能测试(输入的数组中有相同的数字;输入的数组中没有相同的数字)。
b.边界值测试(输入的k等于1或者等于数组的长度)。
c.特殊输入测试(k小于1;k大于数组的长度;指向数组的指针为nullptr)。

参考:

http://www.cnblogs.com/AndyJee/p/4672862.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值