[LeetCode]40.最小的k个数(TopK问题。通过维护堆、优先队列、快排思想等解决方法)

news/2024/5/19 21:47:52 标签: 数据结构, 队列, 算法, 快速排序, 排序算法

最小的k个数

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

示例 1:

输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]

示例 2:

输入:arr = [0,1,2,1], k = 1
输出:[0]

限制:

0 <= k <= arr.length <= 10000
0 <= arr[i] <= 10000




思路

1.暴力法,先对数组进行排序(各种排序方法),再取出前k个数。

public int[] getLeastNumbers(int[] arr, int k) {
    Arrays.sort(arr);
    int[] ints = new int[k];
    for (int i = 0; i < k - 1; i++) {
        ints[i]=arr[i];
    }
    return ints;
}

2.建立大根堆解决前K小问题.(小根堆解决前K大问题)。维护一个大根堆,若堆的大小小于K,将当前值放入队中。否则判断当前值与堆顶元素的大小,如果当前值小于堆顶元素,再将当前值放入堆中,每放入一个值后,再要调整成为大根堆。

    public  int[] getLeastNumbers(int[] arr,int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        int[] ints = Arrays.copyOf(arr, k);
        //构建k个数的大根堆,堆顶为最大的数,for循环确保每个子堆都是大根堆,至少有ints.length/ 2个子堆。
        for (int i =ints.length/ 2; i >= 0; i--) {
            setHeap(ints, i, k);
        }

        //每次取数组中剩下的数与堆顶的数比较
        for (int i = k; i <arr.length; i++) {
            //如果数组中的数比堆顶的数小,则放入堆顶,再构建一次大根堆
            if (ints[0]>arr[i]){
                ints[0]=arr[i];
                setHeap(ints,0,k);
            }
        }
        return ints;
    }

    public  void  setHeap(int [] array,int parent,int length){
        int temp=array[parent];
        int child=parent*2+1;
        //循环判断父节点的值是否小于子节点,是则替换
        while (length>child){
            //取出子节点中较大的数的索引
            if(child+1<length && array[child]<array[child+1]){
                child++;
            }
            //如果父节点值大于子节点则不用交换值
            if(temp>=array[child]){
                break;
            }
            //交换父节点和子节点的值
            array[parent]=array[child];
            parent=child;
            child=2*child+1;
        }
        array[parent]=temp;
    }

3.使用优先队列代替堆,优先队列的实现原理与大根堆/小根堆相同。效率不如手写堆。(也许是调用库函数会加载其他东西)

public int[] getLeastNumbers(int[] arr, int k) {
        if ( k==0 || arr.length==0){
            return new int[0];
        }
        PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> o2-o1);
        //优先队列默认为升序排列,重写compare方法为降序排列。使用lombda表示,等价于以下代码
//        PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
//            @Override
//            public int compare(Integer o1, Integer o2) {
//                return o2-o1;
//            }
//        });

        for (int i : arr) {
            if (queue.size()<k){
                queue.offer(i);
            }else if (queue.peek()>i){ //当优先队列满k个后,取最大值和待放入的值比较,如果待放入值小,则放入。
                queue.poll();
                queue.offer(i);
            }
        }

        int[] ints = new int[queue.size()];
        for (int i = 0; i < ints.length; i++) {
            ints[i]=queue.poll();
        }
        
        return ints;
    }

4.快速排序思想,但是不对数组完全排序,只需要有选择性的分段排序,当确定基准等于K时,则K左边的数都比k小。右边的数不需要处理。

    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0 || arr.length == 0) {
            return new int[0];
        }
        //k-1为我们要找的基准的下标。
        return quickSort(arr, 0, arr.length - 1, k - 1);
    }

    public int[] quickSort(int[] arr, int left, int right, int k) {
        // 对数组进行分割,取出下次分割的基准标号
        int division = division(arr, left, right);
        //如果基准与k正好相等,则返回k左边的部分。
        if (division == k) {
            return Arrays.copyOf(arr, k + 1);
        }
        // 如果k在基准的右边,则对右段进行递归排序。
        // 如果k在基准的坐边,则对左段进行递归排序。
        return division < k ? quickSort(arr, division + 1, right, k) : quickSort(arr, left, division - 1, k);

    }

    public int division(int[] list, int left, int right) {
        // 以最左边的数(left)为基准
        int base = list[left];
        while (left < right) {
            // 从序列右端开始,向左遍历,直到找到小于base的数
            while (left < right && list[right] >= base) {
                right--;
            }
            // 找到了比base小的元素,将这个元素放到最左边的位置
            list[left] = list[right];

            // 从序列左端开始,向右遍历,直到找到大于base的数
            while (left < right && list[left] <= base) {
                left++;
            }
            // 找到了比base大的元素,将这个元素放到最右边的位置
            list[right] = list[left];
        }

        // 最后将base放到left位置。都比此时,left位置的左侧数值应该left小;
        // 而left位置的右侧数值应该都比left大。
        list[left] = base;
        return left;
    }

5.题目中规定数字不大于一万,可以使用频次数字处理,然后遍历频次数组,获取前K个数。

public int[] getLeastNumbers(int[] arr, int k) {
    if (k == 0 || arr.length == 0) {
        return new int[0];
    }
    // 统计每个数字出现的次数
    int[] hash = new int[10001];
    for (int num : arr) {
        hash[num]++;
    }
    
    int[] ans = new int[k];
    int count=0;
    for (int num = 0; num < hash.length; num++) {
        if (count == k) {
            break;
        }
        //从频次数组中取出前k个数。
        while (hash[num]>0 && k>count){
            ans[count++]=num;
            hash[num]--;
        }
    }
    
    return ans;
}

http://www.niftyadmin.cn/n/625683.html

相关文章

01.一条SQL查询语句是如何执行的?

原文&#xff1a;极客时间《MySQL 实战 45 讲》专栏 这是专栏的第一篇文章&#xff0c;我想来跟你聊聊MySQL的基础架构。我们经常说&#xff0c;看一个事儿千万不要直接陷入细节里&#xff0c;你应该先鸟瞰其全貌&#xff0c;这样能够帮助你从高维度理解问题。同样&#xff0c;…

02.一条SQL更新语句是如何执行的?

原文&#xff1a;极客时间《MySQL 实战 45 讲》专栏 前面我们系统了解了一个查询语句的执行流程&#xff0c;并介绍了执行过程中涉及的处理模块。相信你还记得&#xff0c;一条查询语句的执行过程一般是经过连接器、分析器、优化器、执行器等功能模块&#xff0c;最后到达存储引…

事务隔离级别和传播行为、Spring如何管理事务

1.什么是事务 事务简单来说就是将一串操作一起提交&#xff0c;他们要么都执行成功&#xff0c;要么都执行失败。 事务四大特性&#xff1a; 原子性(Atomicity)&#xff1a;事务是一个原子操作&#xff0c;操作要么都成功&#xff0c;要么都失败。一致性(Consistency)&#…

[LeetCode]445. 两数相加 II(使用链表翻转或两个辅助栈)

两数相加 II 给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。 你可以假设除了数字 0 之外&#xff0c;这两个数字都不会以零开头。 示例&#xff1a; 输入&#xff1a;(7 -> 2 -&g…

Java中处理集合利器------stream流

使用Stream常常可以避免一些for循环造成的代码臃肿。在实际开发中&#xff0c;遇到可以用Stream处理的场景也越来越多&#xff0c;源码还未参透。在这只总结一些常用到的方法。 Stream的常用方法 常用方法用到的集合&#xff1a; List<Integer> integers new ArrayLis…

二叉树、平衡二叉树、红黑树、BTree、B+Tree的区别和联系

1.二叉查找树 二叉树具有以下性质&#xff1a;左子树的键值小于根的键值&#xff0c;右子树的键值大于根的键值。 如下图所示就是一棵二叉查找树&#xff0c; 对该二叉树的节点进行查找发现深度为1的节点的查找次数为1&#xff0c;深度为2的查找次数为2&#xff0c;深度为n的…

反射常用方法总结和基于反射的动态代理

1.概念 对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法&#xff1b;对于任意一个对象&#xff0c;都能够调用它的任意一个方法。 2.主要功能&#xff1a; 在运行时判断任意一个对象所属的类&#xff1b;在运行时构造任意一个类的对象&#xff1b;在运行时判断…

java.lang.IllegalStateException: Duplicate key异常问题

前两天刚刚总结完stream流处理Java集合数据&#xff0c;今天就遇到坑了&#xff0c;在处理list数据的时候抛出异常&#xff1a; 存在有重复的key16407&#xff0c;查看日志定位到这一行代码&#xff1a; Map<String, Long> codeAndId groups.stream().collect(Collecto…