力扣215:数组中的第K个最大元素(Java快速查找、计数排序、堆排序)

简介: 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

一、题目描述



给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。


示例 1:

输入: [3,2,1,5,6,4], k = 2

输出: 5


示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4

输出: 4


提示:

1 <= k <= nums.length <= 105

-104 <= nums[i] <= 104


二、思路讲解



2.1 方法一:暴力


首先很容易想到将数组排序,然后找到第k大的数字。

public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }


时间复杂度:        O(NlogN)

空间复杂度:        O(1)


虽然可以通过,但是快速排序的时间复杂度达到了NlogN。我们还需要降低时间复杂度。


2.2 方法二:快速选择


根据快速排序的知识我们可以知道,每次partition算法会将所选的基准数(记为target)放到正确的位置,而我们其实只需要知道第k大的位置上的数是什么,而不需要关心其他位置是否有序。那么我们可以根据基准数的位置来判断,假如k在target的左边,那么我们只需要递归target左边即可,而不需要关心target右边是否有序;反之,如果k在target右边,我们只需要递归右边。

class Solution {
    public int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length-1, nums.length-k);
    }
    /**
        partition算法
     */
    int partition(int []nums, int i, int j) {
        int target = nums[i];
        while(i<j) {
            while(i<j && nums[j]>=target) {
                j--;
            }
            nums[i] = nums[j];
            while(i<j && nums[i]<=target) {
                i++;
            }
            nums[j] = nums[i];
        }
        nums[i] = target;
        return i;
    }
    /**
        快速选择算法
     */
    int quickSelect(int []nums, int i, int j, int index) {
        if(i>=j) {
            return nums[i];
        }
        int k = partition(nums, i, j);
        if(k == index) {
            return nums[k];
        } else if(k < index) {
            //只递归右半边
            return quickSelect(nums, k+1, j, index);
        } else {
            //只递归左半边
            return quickSelect(nums, i, k-1, index);
        }
    }
}


时间复杂度:        O(N)

空间复杂度:        O(logN)        递归使用栈空间的空间代价的期望为 O(logn)


2.3 方法三:堆排序


了解堆排序的朋友们应该可以想到,构建一次大顶堆,堆顶元素就是数组中最大的数,构建第二次大顶堆,堆顶元素就是第二大的数……那么,我们构建k次大顶堆,堆顶元素就是第k大的数了。

class Solution {
    public int findKthLargest(int[] nums, int k) {        
        return heapSort(nums, k);
    }
    int heapSort(int []nums, int k) {
        for (int i = nums.length/2-1; i >= 0; i--) {
            adjustHeap(nums, i, nums.length);
        }
        //操作k-1次,即可找到第k大的元素
        for (int i=nums.length-1; i>nums.length-k-1; i--) {
            int temp = nums[i];
            nums[i] = nums[0];
            nums[0] = temp;
            adjustHeap(nums, 0, i);
        }
        return nums[nums.length-k];
    }
    void adjustHeap(int []nums, int i, int length) {
        int temp = nums[i];
        for (int k=2*i+1; k<length; k=2*k+1) {
            if ((k+1)<length && nums[k]<nums[k+1]) {
                k++;
            }
            if (nums[k]>temp) { 
                nums[i] = nums[k];   
                i = k;  
            } else {
                break;  
            }
        }
        nums[i] = temp;
    }
}


2.4 方法四:计数排序

     

题目中只要求时间,没有要求空间,且数字的范围不算很大,那就很适合计数排序了。思想很简单,就不过多介绍了。


class Solution {
    public int findKthLargest(int[] nums, int k) {
        //考虑负数
        int []count = new int[20001];
        for(int num : nums) {
            count[num+10000]++;
        }
        for(int i=count.length-1; i>=0; i--) {
            k -= count[i];
            if(k<=0) {
                return i-10000;
            }
        }
        return -1;
    }
}


时间复杂度:        O(N)

     

空间复杂度:        O(N)

目录
打赏
0
0
0
0
178
分享
相关文章
除了变量,final还能修饰哪些Java元素
在Java中,final关键字不仅可以修饰变量,还可以用于修饰类、方法和参数。修饰类时,该类不能被继承;修饰方法时,方法不能被重写;修饰参数时,参数在方法体内不能被修改。
100 3
|
1月前
|
Go
【LeetCode 热题100】DP 实战进阶:最长递增子序列、乘积最大子数组、分割等和子集(力扣300 / 152/ 416 )(Go语言版)
本文深入解析三道经典的动态规划问题:**最长递增子序列(LIS)**、**乘积最大子数组** 和 **分割等和子集**。 - **300. LIS** 通过 `dp[i]` 表示以第 `i` 个元素结尾的最长递增子序列长度,支持 O(n²) 动态规划与 O(n log n) 的二分优化。 - **152. 乘积最大子数组** 利用正负数特性,同时维护最大值与最小值的状态转移方程。 - **416. 分割等和子集** 转化为 0-1 背包问题,通过布尔型 DP 实现子集和判断。 总结对比了三题的状态定义与解法技巧,并延伸至相关变种问题,助你掌握动态规划的核心思想与灵活应用!
83 1
打乱数组内容引发的问题( Java)
本文介绍了两种实现数组随机打乱的方法,并深入探讨了Java中原始数据类型与对象类型的差异。方法一通过自定义随机数交换数组元素位置,方法二借助`Collections.shuffle()`函数完成数组打乱。同时,文章详细解析了`int`和`Integer`的区别,包括声明方式、内存占用、初始化以及对象特性等,并讲解了自动装箱与拆箱的功能,帮助读者更好地理解Java的基础知识。
Java 中数组的多种定义方式
本文深入解析了Java中数组的多种定义方式,涵盖基础的`new`关键字创建、直接初始化、动态初始化,到多维数组、`Arrays.fill()`方法以及集合类转换为数组等高级用法。通过理论与实践结合的方式,探讨了每种定义方法的适用场景、优缺点及其背后的原理,帮助开发者掌握高效、灵活的数组操作技巧,从而编写更优质的Java代码。
132 0
|
9月前
|
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
108 3
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
这篇文章详细解析了力扣热题 347——前 K 个高频元素的三种解法:哈希表+小顶堆、哈希表+快速排序和哈希表+桶排序。每种方法都附有清晰的思路讲解和 Go 语言代码实现。小顶堆方法时间复杂度为 O(n log k),适合处理大规模数据;快速排序方法时间复杂度为 O(n log n),适用于数据量较小的场景;桶排序方法在特定条件下能达到线性时间复杂度 O(n)。文章通过对比分析,帮助读者根据实际需求选择最优解法,并提供了完整的代码示例,是一篇非常实用的算法学习资料。
258 90
Java 中数组Array和列表List的转换
本文介绍了数组与列表之间的相互转换方法,主要包括三部分:1)使用`Collections.addAll()`方法将数组转为列表,适用于引用类型,效率较高;2)通过`new ArrayList&lt;&gt;()`构造器结合`Arrays.asList()`实现类似功能;3)利用JDK8的`Stream`流式计算,支持基本数据类型数组的转换。此外,还详细讲解了列表转数组的方法,如借助`Stream`实现不同类型数组间的转换,并附带代码示例与执行结果,帮助读者深入理解两种数据结构的互转技巧。
123 1
Java 中数组Array和列表List的转换
Java 复制数组
本文介绍了Java中数组的基础知识与常用操作,包括数组的概念、创建、访问元素、遍历、复制、排序和搜索等方法。同时详细讲解了数组的五种赋值方式,并通过代码示例演示了求总和平均值、最大最小值、升序降序排序及Arrays类的常用方法。内容深入浅出,适合初学者学习掌握Java数组的核心功能与应用场景。
|
4月前
|
《从头开始学java,一天一个知识点》之:数组入门:一维数组的定义与遍历
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。明日预告:《多维数组与常见操作》。 通过实例讲解数组的核心认知、趣味场景应用、企业级开发规范及优化技巧,帮助你快速掌握Java数组的精髓。
100 23
Java数组:静态初始化与动态初始化详解
本文介绍了Java中数组的定义、特点及初始化方式。
341 12
AI助理
登录插画

登录以查看您的控制台资源

管理云资源
状态一览
快捷访问

你好,我是AI助理

可以解答问题、推荐解决方案等