求无序数组第 K 大的数

news/2024/5/20 7:57:19 标签: 算法, 数据结构, LeetCode, , BFPRT

求无序数组第 K 大的数

作者:Grey

原文地址:

博客园:求无序数组第 K 大的数

CSDN:求无序数组第 K 大的数

问题描述

无序数组求第K大的数,其中K1开始算。

例如:[0,3,1,8,5,2]这个数组,第2大的数是5

OJ可参考:LeetCode 215. Kth Largest Element in an Array

解法

设置一个小根,先把前K个数放入小根,对于这前K个数来说,顶元素一定是第K大的数,接下来的元素继续入,但是每入一个就弹出一个,最后,顶元素就是整个数组的第K大元素。代码如下:

class Solution {
    public static int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> heap = new PriorityQueue<>();
        for (int i = 0; i < k; i++) {
            heap.offer(nums[i]);
        }
        for (int i = k; i < nums.length;i++) {
            heap.offer(nums[i]);
            heap.poll();
        }
        return heap.peek();
    }
}

由于每次需要承担logK的调整代价, 所以这个解法的时间复杂度为O(N*logK)

改进快排算法

快速排序中,有一个partition的过程, 这个过程随机选择数组中的一个数,假设叫pivot,这个过程主要作用是将数组的l...r区间内的数:

  • 小于pivot的数放右边

  • 大于pivot的数放左边

  • 等于pivot的数放中间

返回两个值,一个是左边界和一个右边界,位于左边界和右边界的值均等于pivot,小于左边界的位置的值都大于pivot,大于右边界的位置的值均小于pivot。简言之:如果要排序,pivot这个值在一次partition以后,所在的位置就是最终排序后pivot应该在的位置。

所以,如果数组中某个数在经历上述partion之后正好位于K-1位置,那么这个数就是整个数组第K大的数。

快排改进算法完整代码如下:

class Solution {
       public static int findKthLargest(int[] nums, int k) {
        return process(nums, 0, nums.length - 1, k - 1);
    }

    // arr 在L...R范围内,如果要从大到小排序,请返回index位置的值
    private static int process(int[] arr, int L, int R, int index) {
        if (L == R) {
            return arr[R];
        }
        int pivot = arr[L + (int) (Math.random() * (R - L + 1))];
        int[] range = partition(arr, L, R, pivot);
        if (index >= range[0] && index <= range[1]) {
            return pivot;
        } else if (index < range[0]) {
            return process(arr, L, range[0], index);
        } else {
            return process(arr, range[1], R, index);
        }
    }

    public static int[] partition(int[] arr, int L, int R, int pivot) {
        int less = L - 1;
        int more = R + 1;
        while (L < more) {
            if (arr[L] > pivot) {
                swap(arr, L++, ++less);
            } else if (arr[L] == pivot) {
                L++;
            } else {
                swap(arr, L, --more);
            }
        }
        return new int[]{less + 1, more - 1};
    }
    
    public static void swap(int[] nums, int t, int m) {
        int tmp = nums[m];
        nums[m] = nums[t];
        nums[t] = tmp;
    }
}

其中process方法表示:numsL...R范围上,如果要排序(从大到小)的话,请返回index位置的值。

int pivot = nums[L + (int) (Math.random() * (R - L + 1))];

这一行表示随机取一个值pivot出来,用这个值做后续的partition操作,如果index恰好在pivot这个值做partition的左右边界范围内,则pivot就是排序后第index+1大的数(从1开始算)。

bfprt算法

brfpt算法和改进快排算法主流程上基本一致,只是在选择pivot的时候有差别,快排改进是随机取一个数作为pivot, 而bfprt算法是根据一定的规则取pivot,核心代码为:

public class LeetCode_0215_KthLargestElementInAnArray {
    ......
    // nums在L...R范围上,如果要排序(从大到小)的话,请返回index位置的值
    public static int bfprt(int[] nums, int L, int R, int index) {
        if (L == R) {
            return nums[L];
        }
        //int pivot = nums[L + (int) (Math.random() * (R - L + 1))];
        int pivot = medianOfMedians(nums, L, R);
        int[] range = partition(nums, L, R, pivot);
        if (index >= range[0] && index <= range[1]) {
            return pivot;
        } else if (index < range[0]) {
            return bfprt(nums, L, range[0] - 1, index);
        } else {
            return bfprt(nums, range[1] + 1, R, index);
        }
    }
    ......
}

其中

int pivot = medianOfMedians(nums, L, R);

就是bfprt算法最关键的步骤,mediaOfMedians这个函数表示:

num分成每五个元素一组,不足一组的补齐一组,并对每组进行排序(由于固定是5个数一组进行排序,所以排序的时间复杂度O(1)),取出每组的中位数,组成一个新的数组, 对新的数组求其中位数,这个中位数就是我们需要的值pivot

    public static int medianOfMedians(int[] arr, int L, int R) {
        int size = R - L + 1;
        int offSize = size % 5 == 0 ? 0 : 1;
        int[] mArr = new int[size / 5 + offSize];
        for (int i = 0; i < mArr.length; i++) {
            // 每一组的第一个位置
            int teamFirst = L + i * 5;
            int median = getMedian(arr, teamFirst, Math.min(R, teamFirst + 4));
            mArr[i] = median;
        }
        return bfprt(mArr, 0, mArr.length - 1, (mArr.length - 1) / 2);
    }

    public static int getMedian(int[] arr, int L, int R) {
        Arrays.sort(arr, L, R);
        return arr[(R + L) / 2];
    }

注:mediaOfMedians方法中最后一句:

return bfprt(mArr, 0, mArr.length - 1, (mArr.length - 1) / 2);

就是利用bfprt算法拿整个元素中间位置的值。

关于 bfprt 算法的两个问题

  1. 为什么是5个一组

  2. 为什么严格收敛到O(N)

请参考:

BFPRT算法原理

BFPTR算法详解+实现+复杂度证明

三种解法复杂度分析

算法时间空间
O(N*logK)O(N)
快排改进概率上收敛到:O(N)O(1)
bfprt严格收敛到:O(N)O(N)

相关题目

寻找两个正序数组的中位数

笔记见:寻找两个正序数组中的中位数

第 K 小的数值对

长度为N的数组arr,一定可以组成N^2个数值对。例如arr = [3,1,2],数值对有(3,3) (3,1) (3,2) (1,3) (1,1) (1,2) (2,3) (2,1) (2,2),也就是任意两个数都有数值对,而且自己和自己也算数值对。数值对怎么排序?规定,第一维数据从小到大,第一维数据一样的,第二维数组也从小到大。所以上面的数值对排序的结果为:(1,1)(1,2)(1,3)(2,1)(2,2)(2,3)(3,1)(3,2)(3,3), 给定一个数组arr,和整数k,返回第k小的数值对。

代码见:Code_0015_KMinPair

更多

算法数据结构笔记

参考资料

程序员代码面试指南(第2版)

算法数据结构体系班-左程云

BFPRT算法原理

BFPTR算法详解+实现+复杂度证明


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

相关文章

Spring MVC -- 数据绑定和表单标签库

数据绑定是将用户输入绑定到领域模型的一种特性。有了数据绑定&#xff0c;类型总是为String的HTTP请求参数&#xff0c;可用于填充不同类型的对象属性&#xff08;或者说字段&#xff09;。数据绑定使得form bean&#xff08;在前面几篇博客案例中的表单类ProductForm实例&…

蓄水池算法的设计和实现

蓄水池算法的设计和实现 作者: Grey 原文地址&#xff1a; 博客园&#xff1a;蓄水池算法的设计和实现 CSDN&#xff1a;蓄水池算法的设计和实现 要解决的问题 假设有一个源源吐出不同球的机器&#xff0c; 只有装下10个球的袋子&#xff0c;每一个吐出的球&#xff0c;要…

IDEA-java web工程配置流程

Intellij IDEA2021.1 点击next 填写项目的名称以及位置&#xff0c;finish 右键项目&#xff0c;选择add framework support 完成之后&#xff0c;项目结构变成了这样 接下来&#xff0c;我们在WEB-INF下创建classes,lib文件夹 编辑项目结构 将output path的路径改成classes文…

静态绑定

对象调用方法时&#xff0c;在编译时已经确定。转载于:https://www.cnblogs.com/q2546/p/10833251.html

资源限制类问题的常用解决方案

资源限制类问题的常用解决方案 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;资源限制类问题的常用解决方案 CSDN&#xff1a;资源限制类问题的常用解决方案 问题1 32位无符号整数的范围内有 4294967295 个数&#xff0c;现在有一个正好包含 40 亿个无符…

java web Servlet学习

servlet实现过程 1.编写一个类去实现Servlet接口 2.实现service方法&#xff0c;处理请求并响应数据 3.到web.xml中去配置Servlet程序的访问地址 servlet程序的xml文件配置 <servlet><servlet-name>HelloServlet</servlet-name><!--servlet-name标签…

Java_静态变量

class c1c {private static int num 0;private static double pi 3.14;private double radius;private int height;public c1c(double r,int h){radius r;heighth;num;}public void count(){System.out.print("创建了"num"个对象");}double area() {ret…

使用线段树解决数组任意区间元素修改问题

使用线段树解决数组任意区间元素修改问题 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;使用线段树解决数组任意区间元素修改问题 CSDN&#xff1a;使用线段树解决数组任意区间元素修改问题 要解决的问题 数组任意区间内的元素修改&#xff0c;增加&…