算法题:数组中的第k个最大元素

力扣链接

题意

给定整数数组 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

思路及解答

堆排序

这道题就是经典的 topK 问题, 实现一个小根堆 ,规定堆的元素大小是 k 个,将比堆顶大的元素进堆。最后遍历完数组之后,此时堆顶是 k 个元素中最小的,有就是所有元素中第 k 大的了。

可以用PriorityQueue的最小堆来实现:

java 复制代码
public class KthLargestMinHeap {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
        
        for (int num : nums) {
            if (minHeap.size() < k) {
                minHeap.offer(num);
            } else if (num > minHeap.peek()) {
                minHeap.poll();
                minHeap.offer(num);
            }
        }
        return minHeap.peek();
    }
}

升序排序后索引为 len - k 的元素

其实题目已经告诉我们了:

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

因此,升序排序以后,返回索引为 len - k 这个元素即可。

代码如下:

java 复制代码
public class Solution {
 
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        Arrays.sort(nums);
        return nums[len - k];
    }
}
  • 时间复杂度:O(NlogN) 。这里 N 是数组的长度,算法的性能消耗主要在排序,JDK 默认使用快速排序,因此时间复杂度为O(NlogN)。
  • 空间复杂度:O(1)。这里是原地排序,没有借助额外的辅助空间。

但是,如果出了这道题,显然不是想让你调 API ,而是看你对快排的理解

借助快排 partition 操作定位(推荐)

"快速排序" 中的 partition(切分)操作简单介绍如下:

  • 对于某个索引i,nums[i] 已经排序完,即 nums[i] 经过 partition(切分)操作以后会放置在它 "最终应该放置的地方";
  • nums[left] 到 nums[i - 1] 中的所有元素都不大于 nums[i];
  • nums[i + 1] 到 nums[right] 中的所有元素都不小于 nums[i]。

很显然,nums[i] 最终所在的位置,也就是它排序后的具体位置。也就是说,如果实现的排序是降序排序,那么第k个位置的数据,也确定就是第k大的

而这样每经过一次 partition操作就能缩小搜索的范围,这样的思想叫做 "减而治之"(是 "分而治之" 思想的特例)。下面是参考代码:

javascript 复制代码
public class Solution {
 
    public int findKthLargest(int[] nums, int k) {
        int len = nums.length;
        int left = 0;
        int right = len - 1;
 
        // 转换一下,第 k 大元素的索引是 len - k
        int target = len - k;
 
        while (true) {
            int index = partition(nums, left, right);
            if (index == target) {
                return nums[index];
            } else if (index < target) {
                left = index + 1;
            } else {
                right = index - 1;
            }
        }
    }
 
	public static int partition(int[] array, int low, int high) {
	    // 取最后一个元素作为中心元素
	    int pivot = array[high];
	    // 定义指向比中心元素大的指针,首先指向第一个元素
	    int pointer = low;
	    // 遍历数组中的所有元素,将比中心元素大的放在右边,比中心元素小的放在左边
	    for (int i = low; i < high; i++) {
	        if (array[i] <= pivot) {
		        // 将比中心元素小的元素和指针指向的元素交换位置 
		        // 如果第一个元素比中心元素小,这里就是自己和自己交换位置,指针和索引都向下一位移动 
		        // 如果元素比中心元素大,索引向下移动,指针指向这个较大的元素,直到找到比中心元素小的元素,并交换位置,指针向下移动
		        swap(array, i, pointer);
	            pointer++;
	        }
	    }
	    // 将中心元素和指针指向的元素交换位置
	    swap(array, pointer, high);
	    return pointer;
	}
	
	private static void swap(int[] arr, int i, int j) { 
		int temp = arr[i]; 
		arr[i] = arr[j]; 
		arr[j] = temp; 
	}
}
  • 时间复杂度:O(N)。这里 N 是数组的长度。
  • 空间复杂度:O(1)。切分过程可以不借助额外的数组空间,仅通过交换数组元素实现,没有借助额外的辅助空间。
相关推荐
打野二师兄17 分钟前
Spring Boot 自动配置:从 2.x 到 3.x 的进化之路
java·spring boot·后端
吃着火锅x唱着歌43 分钟前
LeetCode 1616.分割两个字符串得到回文串
算法·leetcode·职场和发展
Monkey-旭1 小时前
Android JNI 语法全解析:从基础到实战
android·java·c++·c·jni·native
魑魅魍魉都是鬼1 小时前
随缘玩 一: 代理模式
android·java·代理模式
你我约定有三1 小时前
分布式微服务--RPC:原理、使用方式、与 HTTP/REST 的区别与选择
java·开发语言·分布式·后端·微服务·rpc
loop lee2 小时前
【JVM】常见的 Java 垃圾回收算法以及常见的垃圾回收器介绍及选型
java·jvm·算法
RainbowSea2 小时前
伙伴匹配系统(移动端 H5 网站(APP 风格)基于Spring Boot 后端 + Vue3 - 02
java·vue.js·spring boot
工业互联网专业2 小时前
基于JavaWeb的兼职发布平台的设计与实现
java·vue.js·spring boot·毕业设计·源码·课程设计·兼职发布平台
程序员小潘2 小时前
Dubbo 序列化类检查和自动信任机制
java·dubbo
Seven973 小时前
剑指offer-17、树的⼦结构
java