LeetCode 215.数组中的第K个最大元素

本文介绍了如何在整数数组中寻找第K大的元素,提供了插入排序、快速排序和堆排序三种解题方法。通过优化的快速排序,可以在平均情况下达到O(nlogn)的时间复杂度。此外,还详细解释了每种排序算法的实现过程和关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目:给定一个整数数组nums和一个整数k,要求返回数组中第k个大的元素。

例如:输入[3, 2, 1, 5, 6, 4]k=2则输出为5。

解题思路

只求AC笔试必挂的解题思路:

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

哈哈~ 实际上本题考查的是对排序的理解。下面介绍一下几种排序:

插入排序

插入排序的思想是:在待排序的元素中,假设前面的n-1个数已经排好序,现将第n个数插入已排好的序列中,使得插入后的序列依然有序。根据插入排序本题的代码为:

// 插入排序
public static int findKthLargest(int[] nums, int k) {
    if(nums.length==1&&k==1) return nums[0];
    for(int i=1;i<nums.length;i++){
        int temp = nums[i];
        int index = i-1;
        while(index>=0&&nums[index]>temp){
            nums[index+1] = nums[index];
            index--;
        }
        nums[index+1] = temp;
    }
    return nums[nums.length-k];
}

插入排序的时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)

快速排序

快速排序的思想是每次找一个基准值,此基准值将数组分为左右两部分,使得数组在基准值左侧的数据小于基准值,在基准值右侧的数据大于基准值。之后分别对数组的左右两部分进行同样的处理。

最简单的快速排序是使用数组的第一个元素作为基准值,之后首先从数组的右边往左边扫描,如果遇到数组的元素小于基准值,则停下。之后从数组的左边向右边扫描,找到第一个大于基准值的数据。交换左右两个值。循环中始终保持左指针数值小于右指针。当两个指针相遇的时候即为基准值应该在的位置。然后递归左右两部分即可,可得代码如下:

public void quicksort(int[] nums, int left, int right){
    if(left>right) return;
    int temp = nums[left];
    int i = left, j = right;
    while(i!=j){
        while(i<j&&nums[j]>=temp) j--;
        while (i<j&&nums[i]<=temp) i++;
        swap(nums, i, j);
    }
    nums[left] = nums[i];
    nums[i] = temp;
    quicksort(nums,left, i-1);
    quicksort(nums,i+1, right);
}

上述代码的时间复杂度为 O ( n 2 ) O(n^2) O(n2),空间复杂度为 O ( l o g 2 n ) O(log_2n) O(log2n)。快速排序是可以优化的,根据基准值的选取不同,时间复杂度也不同。我们可以设置基准值随机选取,这样时间复杂度可降到 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),代码如下:

public void quicksort2(int[] nums, int left, int right){
    if(left>right) return;
    int index = new Random().nextInt(right-left+1)+left;
    int temp = nums[index];
    swap(nums, left, index); // 精髓!!!此处必须交换
    int i = left, j = right;
    while(i!=j){
        while(i<j&&nums[j]>=temp) j--;
        while (i<j&&nums[i]<=temp) i++;
        swap(nums, i, j);
    }
    swap(nums, i, left);
    quicksort2(nums,left, i-1);
    quicksort2(nums,i+1, right);
}

还有些方法使用的是使用数组中间的值作为基准值,这几种变体的核心都是将所选基准值和数组最左边元素进行替换!这一步很重要。由变体的快速排序可获得代码,但题目需要返回的是数组中第K个最大的元素,因此可以对结果进行剪枝,代码如下:

// 快速排序
public static int findKthLargest2(int[] nums, int k) {
    int left=0, right = nums.length-1;
    int index = nums.length-k;
    return quicksort3(nums, left, right, index);
}

public static int quicksort3(int[] nums, int left, int right, int k){
    int index = new Random().nextInt(right-left+1)+left;
    int temp = nums[index];
    swap(nums, left, index); // 精髓!!!此处必须交换
    int i = left, j = right;
    while(i!=j){
        while(i<j&&nums[j]>=temp) j--;
        while (i<j&&nums[i]<=temp) i++;
        swap(nums, i, j);
    }
    swap(nums, i, left);
    if(i==k) return nums[i];
    else if(i>k) return quicksort3(nums,left, i-1, k);
    else return quicksort3(nums,i+1, right, k);
}

此时不会遇到left>right的情况了。

堆排序

堆排序包括堆的建立和堆中元素的删除,其中堆的建立较为简单,因为堆的本质是由数组实现的,因此对于索引为i的元素,其左子树的索引在数组中为2*i+1,而右子树则为2*i+2,其父亲结点为(i-1)/2,因此每次插入元素只需跟其父亲结点进行比对即可,代码如下:

public void buildHeap(int[] heap, int index){
    while(heap[index]>heap[(index-1)/2]){
        swap(heap, index, (index-1)/2);
        index = (index-1)/2 ;
    }
}

堆中元素的删除则较为复杂,删除堆中的元素,我们首先需要将堆的根节点和最后一个叶子结点进行互换,之后需要将此堆再次调整为大根堆,调整的步骤为首先找到根节点的左右子树结点中较大的那个,注意此时不一定比根节点大,因此需要比对,如果大则和根节点互换,之后再次找到互换后的结点的左右子树结点,依次循环,代码如下:

public void heapify(int[] heap, int index, int size){
    int left = 2*index+1;
    while(left<size){
        int largest = left+1<size && heap[left]<heap[left+1] ? left+1:left;
        largest = heap[largest] > heap[index] ? largest : index; // 这一步要注意
        if(largest==index) break;  // 这一步也要注意
        swap(heap, index, largest);
        index = largest;
        left = 2*index+1;
    }
}

对于本题,我们首先根据堆排序获得一个大根堆,之后依次删除元素,保证删除k-1个元素就停止,此时输出的nums[0]即为最终答案,代码如下:

public int findKthLargest3(int[] nums, int k) {
    int len = nums.length;
    for(int i=1;i<len;i++){
        buildHeap(nums,i);
    }
    if(k==1) return nums[0];
    swap(nums, 0, --len);
    while(len>0){
        heapify(nums,0, len);
        if(len!=nums.length-k+1) {
            swap(nums, 0, --len);
        }else {
            break;
        }
    }
    return nums[0];
}

注意看截断的那里。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码匀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值