剑指29.最小的K个数

题目描述

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

思路

思路1:利用排序。排序后最前面的K个数就是最小的K个数。                                                                       (时间复杂度O(nlogn))

思路2:快排Partition思想(Partition函数既是快排的基础,也可以用来查找n个数中第k大的数)                        (时间复杂度为O(n),缺点是需要修改输入的数组)

        基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。调整之后,位于数组左边的k个数字就是最小的k个数字(这k个数字不一定是排序的)。这种思路虽时间复杂度最优,但会修改输入数组。

思路3:最大堆。用最大堆保存k个数,每次只和堆顶比,如果比堆顶小,删除堆顶,新数入堆。                 (时间复杂度O(nlogk),特别适合处理从海量数据中找出最小的k个数)

       先创建一个大小为k的容器,如果容器中已有的数字小于k个,则直接把这次读入的数据放入容器中;如果容器中已有k个数了,我们找出这已有k个数中的最大值,如果接下来遍历的数值比容器中的最大值小,那么用这个数替换容器中的最大值。可以看出,容器满了之后需要做3件事。1)在k个数中找到最大数;2)有可能在容器中删除最大数;3)有可能要插入一个新数字。当需要在某个数据容器内频繁查找及替换最大值时,我们可以使用最大堆,最大堆中根节点的值总是大于它子树中任意节点的值。每次可以在O(1)的时间内得到已有k个数字的最大值,但需要O(logk)时间完成删除以及插入操作。

 

☆解法2(Partition思想)

import java.util.ArrayList;
public class Solution {
    // 采用快排的partition思想
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if(input == null || k < 1 || k > input.length)
            return res;
        // 在数组中寻找位置为k - 1 的pivot
        int start = 0, end = input.length - 1;
        int index = partition(input,start,end);// 任取一个数字为基准,如果index=k-1,说明左边有k个数了
        while(index != k - 1){
            if(index < k - 1) 
                start = index + 1;  // 说明左边不够k个数,因此对右边进一步划分
            else 
                end = index - 1;   // 说明左边大于k个数,因此对左边进一步划分
            index = partition(input,start,end);
        }
        for(int i = 0; i < k; i++)
            res.add(input[i]);
        return res;
    }
    // partition版本2:进阶版,相对版本1优化不必要的交换
    private int partition(int[] arr, int start, int end){
        int pivot = arr[start];
        while(start < end){
            // 顺序很重要,要先从右往左找,如果右边的数比标准数大
            while(start < end && arr[end] >= pivot)
                end--;
            arr[start] = arr[end]; // 采用替换而不是交换的方式进行操作,使用右边的数字替换左边的数
            // 再从左往右找,如果左边的数比标准数小
            while(start < end && arr[start] <= pivot)
                start++;
            arr[end] = arr[start];  //使用左边的数字替换右边的数
        }
        // 此时,low和high重合了,标准数归位。
        arr[start] = pivot;
        return start;   //返回枢纽所在位置
    }
    
    // partition版本1 :需要交换数据
    // 任取一个数字为基准,进行比较和划分。 返回基准点的index
    private int partition(int[] arr, int start, int end){
        int pivot = arr[start];
        while(start < end){
            while(start < end && arr[end] >= pivot)  // 需要加上等号,大于等于pivot才能通过
                end--;
            swap(arr,start,end);  //交换位置,使得大于pivot的值位于数组右边
            while(start < end && arr[start] <= pivot)  // 需要加上等号,小于等于pivot才能通过
                start++;
            swap(arr,start,end);  //交换位置,使得小于pivot的值位于数组左边
        }
        return start;
    }
    private void swap(int[] arr, int i, int j){
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }
}

Note:举个例子理解一下上述过程~        参考:lron欣

例如输入k=4,数组为(9,5,1,3,8,7,13,11,12)。以9为基准划分后,为(5,1,3,8,7,9,13,11,12),左边小于9的有5个数(9的索引不为3),仍需划分,再对(5,1,3,8,7)进行划分此时以5为划分,则形成(1,3,5,8,7),此时左边小5的只有2个(5的索引不为3),因此对右边进行划分,右边划分(7,8)。此时拿到的值为7的索引,整体上来说,原数组变成了(1,3,5,7,8,9,13,11,12)而7这个地方的索引是3,也就是说,左边的4个较小的数已经拿到了。

 

解法3(最大堆)

import java.util.*;
public class Solution {
    // 用优先队列构造最大堆
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        ArrayList<Integer> res = new ArrayList<>();
        if (input == null || k < 1 || k > input.length)
            return res;
        //PriorityQueue<Integer> maxHeap = new PriorityQueue<>(((o1, o2) -> o2.compareTo(o1))); //o1.compareTo(o2) 从小到大;反过来 从大到小,因此这里建立的是最大堆。
        PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, Collections.reverseOrder());
        for (int num : input){
            if (maxHeap.size() < k){
                maxHeap.add(num);
            }else{
                if (maxHeap.peek() > num){
                    maxHeap.poll();
                    maxHeap.add(num);
                }
            }
        }
        while (!maxHeap.isEmpty())
            res.add(maxHeap.poll());
        return res;
    }
}

Note:以后复习时手写一个最大堆进行动态调整,,不使用优先队列。

 

参考:

原文地址:https://www.cnblogs.com/HuangYJ/p/13510655.html