力扣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)

相关文章
|
23天前
|
存储 缓存 安全
除了变量,final还能修饰哪些Java元素
在Java中,final关键字不仅可以修饰变量,还可以用于修饰类、方法和参数。修饰类时,该类不能被继承;修饰方法时,方法不能被重写;修饰参数时,参数在方法体内不能被修改。
24 2
|
2月前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
44 3
|
2月前
|
Java
在Java的世界里,Set只接纳独一无二的元素。
【10月更文挑战第16天】在Java的世界里,Set只接纳独一无二的元素。本文通过拟人化的手法,讲述了重复元素从初次尝试加入Set被拒绝,到经历挣扎、反思,最终通过改变自己,成为独特个体并被Set接纳的全过程。示例代码展示了这一过程的技术实现。
25 1
|
29天前
|
Java
那些与Java Set擦肩而过的重复元素,都经历了什么?
在Java的世界里,Set如同一位浪漫而坚定的恋人,只对独一无二的元素情有独钟。重复元素虽屡遭拒绝,但通过反思和成长,最终变得独特,赢得了Set的认可。示例代码展示了这一过程,揭示了成长与独特性的浪漫故事。
20 4
|
1月前
|
存储 Java
判断一个元素是否在 Java 中的 Set 集合中
【10月更文挑战第30天】使用`contains()`方法可以方便快捷地判断一个元素是否在Java中的`Set`集合中,但对于自定义对象,需要注意重写`equals()`方法以确保正确的判断结果,同时根据具体的性能需求选择合适的`Set`实现类。
|
1月前
|
存储 算法 Java
为什么Java Set如此“挑剔”,连重复元素都容不下?
在Java的集合框架中,Set是一个独特的接口,它严格要求元素不重复,适用于需要唯一性约束的场景。Set通过内部数据结构(如哈希表或红黑树)和算法(如哈希值和equals()方法)实现这一特性,自动过滤重复元素,简化处理逻辑。示例代码展示了Set如何自动忽略重复元素。
30 1
|
2月前
|
存储 缓存 算法
Java 数组
【10月更文挑战第19天】Java 数组是一种非常实用的数据结构,它为我们提供了一种简单而有效的方式来存储和管理数据。通过合理地使用数组,我们能够提高程序的运行效率和代码的可读性。更加深入地了解和掌握 Java 数组的特性和应用,为我们的编程之旅增添更多的精彩。
33 4
|
2月前
|
存储 缓存 算法
提高 Java 数组性能的方法
【10月更文挑战第19天】深入探讨了提高 Java 数组性能的多种方法。通过合理运用这些策略,我们可以在处理数组时获得更好的性能表现,提升程序的运行效率。
36 2
|
2月前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
【10月更文挑战第16天】Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。通过 hashCode() 和 equals() 方法实现唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 添加和遍历元素,体现了 Set 的高效性和简洁性。
39 4
|
2月前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。它通过 hashCode() 和 equals() 方法确保元素唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 实现这一特性。
32 5