NC119 最小的K个数(java)两个通俗易懂的方法

给定一个长度为 n 的可能有重复值的数组,找出其中不去重的最小的 k 个数。例如数组元素是4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4(任意顺序皆可)。
数据范围:0<=k,100000≤k,n≤10000,数组中每个数的大小 10000≤val≤1000
要求:空间复杂度 O(n) ,时间复杂度 O(nlogn)

示例一

输入:
[4,5,1,6,2,7,3,8],4
复制
返回值:
[1,2,3,4]
复制
说明:
返回最小的4个数即可,返回[1,3,2,4]也可以

示例二

输入:
[1],0
复制
返回值:
[]

示例三

输入:
[0,1,2,1,2],3
复制
返回值:
[0,1,1]

法一:快速排序
算法步骤:将输入数组进行快速排序,若快排一轮结束后,首数字插入位置==k,那么说明前k个数就是最小的k个数,若首数字插入位置<k,那么说明还需要对后面的数字进行快排,若首数字插入位置>k,则需要对前边的数字再进行快排,直到最后数字插入位置为k再返回前k个数字的数组即为结果。
时间复杂度一般情况下O(nlogn),空间复杂度O(1)
易错点:容易忘记快速排序left < right的条件在快排内部也需要出现,也需要进行比较。

import java.util.*;

public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k) {
        ArrayList<Integer> res = new ArrayList<Integer>();
        if(k > input.length || k < 0)
            return res;
        QuickSort(input, res, 0, input.length-1, k);
        return res;
    }
    public void QuickSort(int[] input, ArrayList<Integer> res, int left, int right, int k){
        int start = left;
        int end = right;
        while(left < right){
            while(left < right && input[start] <= input[right])
                right--;
            while(left < right && input[start] >= input[left])
                left++;
            swap(input, left, right);
        }//快速排序
        
        swap(input, start, left);//把首数字移到指针处
        if(k == left){//首数字插入位置==k,那么说明前k个数就是最小的k个数
            for(int i = 0; i < k; i++){
                res.add(input[i]);
            }
        }
        else if(k > left)//若首数字插入位置<k,那么说明还需要对后面的数字进行快排
            QuickSort(input, res, left + 1, end, k);
        else//若首数字插入位置>k,则需要对前边的数字再进行快排
            QuickSort(input, res, start, left - 1, k);
    }
    private void swap(int[] input, int num1, int num2){
        if(num1 == num2)
            return;
        int temp = input[num1];
        input[num1] = input[num2];
        input[num2] = temp;
    }//交换数组内部元素
}

法二 大根堆
算法步骤:将k个数字先初始建堆成为大根堆,即上层数字大于下层数字,再插入余下数字到大根堆中,如果该数字小于堆顶,则弹出堆顶元素插入该数字到堆中,最后排序完成的,有k个数字的堆输出到数组中就是结果。
(该方法参考了:牛客网用户 刷刷题 的代码)
新知识:
PriorityQueue bigHeap = new PriorityQueue<>((num1, num2) -> num2 - num1)是创建大根堆。
(num1, num2) -> num2 - num1代表堆内父子节点元素按照大到小排序 构成大顶堆。
比较器中,num1是要被添加的元素,num2是堆顶元素。

import java.util.*;
 
public class Solution {
    public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
        return bigHeap(input, k);
    }
	public ArrayList<Integer> bigHeap (int [] input, int k) {
 
        ArrayList<Integer> result = new ArrayList<>(k);
 
        //根据题意要求,如果K>数组的长度,返回一个空的数组
        if (k > input.length || k == 0) {
            return result;
        }
 
        /**
         * 创建最大堆 看PriorityQueue的offer源码可知:
         * public boolean offer(E e) {
         *         if (e == null)
         *             throw new NullPointerException();
         *         modCount++;
         *         int i = size;
         *         if (i >= queue.length)
         *             grow(i + 1);
         *         size = i + 1;
         *         if (i == 0)
         *             queue[0] = e;
         *         else
         *             siftUp(i, e);
         *         return true;
         *     }
         * siftUp(i, e)这个方法,当插入的元素不是顶部位置,会进行内容排序调整,siftUp(i, e)方法就是起到这个作用
         * 默认的插入规则中,新加入的元素可能会破坏小顶堆或大顶堆的性质,因此需要进行调整。
         * 调整的过程为:从尾部下标的位置开始,将加入的元素逐层与当前点的父节点的内容进行比较并交换,直到满足父节点内容都小于或大于子节点的内容为止。
         * private void siftUp(int k, E x) {
         *     while (k > 0) {
         *         int parent = (k - 1) >>> 1;//parentNo = (nodeNo-1)/2
         *         Object e = queue[parent];
         *         if (comparator.compare(x, (E) e) >= 0)//调用比较器的比较方法
         *             break;
         *         queue[k] = e;
         *         k = parent;
         *     }
         *     queue[k] = x;
         * }
         *
         * 这里覆盖的比较器,num1是要被添加的元素,num2是堆顶元素,第一次放入第一个元素,直接队列头元素赋值为该第一个元素,后续
         * 放入第N个元素的时候,会执行siftUp 紧随着会执行比较器 根据比较器构建最大堆还是最小堆
         * siftUp(i, e)这个方法:默认的插入规则中,新加入的元素可能会破坏小顶堆或大顶堆的性质,因此需要进行调整。
         * 调整的过程为:从尾部下标的位置开始,将加入的元素逐层与当前点的父节点的内容进行比较并交换,直到满足父节点内容都小于或大于子节点的内容为止。
         *
         * 我们重写的比较器是:comparator:表示比较器对象,如果为空,使用自然排序
         * (num1, num2) -> num2 - num1
         * 所以代表 堆内父子节点元素按照大到小排序 构成大顶堆
         */
        PriorityQueue<Integer> bigHeap = new PriorityQueue<>(new Comparator<Integer>() {
            //结合自定义比较器 构建大顶堆, 如果不重写比较器,那么默认为小顶堆
            @Override
            public int compare (Integer num1, Integer num2) {
                return num2 - num1;
            }
        });
 
        for (int i = 0; i < k; i++) {
            bigHeap.offer(input[i]);
        }
 
        //因为是最大堆,也就是堆顶的元素是堆中最大的,遍历数组后面元素的时候,
        //如果当前元素比堆顶元素小,就把堆顶元素给移除,然后再把当前元素放到堆中
        for (int j = k; j < input.length; j++) {
 
            if (bigHeap.peek() > input[j]) {
                bigHeap.poll();
                bigHeap.offer(input[j]);
            }
        }
 
        for (int h = k - 1; h >= 0; h--) {
            result.add(bigHeap.poll());
        }
 
        return result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值