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

2020-12-20 08:58

阅读:448

标签:部分   渐进   最小堆   mamicode   ==   top   通过   最小   rand   

题目

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例
??输入:[3,2,3,1,2,4,5,5,6] 和 k = 4
??输出:4

TopK问题是一道高频面试题!

解法一:排序+查找

由于数组是未排序的,最直接粗暴的方法就是先对数组进行排序,排序后直接通过索引返回满足条件的元素即可。代码简洁到感人。

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

复杂度:总的时间复杂度就是排序的复杂度:\(O(NlogN)\),没有使用额外的空间,空间复杂度为 \(O(1)\)

解法二:快速排序(单边)

快速排序中,每次partion操作将数组分为两部分,左边小于切分元素,右边大于切分元素。这样我们如果找到这样的一个切分点,partion操作完它右边有 k-1 个元素,那这个切分点就是我们需要的元素。

class Solution {
    Random rand = new Random();
    public int findKthLargest(int[] nums, int k) {
        return findK(nums, 0, nums.length - 1, k);
    }

    private int findK(int[] nums, int lo, int hi, int k){
        int j = partion(nums, lo, hi);
        if(j == k  -1) return nums[j];
        else if(j > k - 1){
                return findK(nums, lo, j - 1, k);
        }
        else{
                return findK(nums, j + 1, hi, k);
        }
    }

    private int partion(int[] nums, int lo, int hi){
        int r = rand.nextInt(hi - lo + 1) + lo;  // 切分点的位置,随机选择很关键
        int tmp = nums[r];   // 哨兵
        nums[r] = nums[lo];
        nums[lo] = tmp;
        while(lo = tmp){
                lo++;
            }
            nums[hi] =  nums[lo];
        }
        nums[lo] = tmp;
        return lo;
    }
}

复杂度:快速排序的复杂度为 \(O(NlogN)\),但是上面的代码仅仅是快速排序的部分实现,其期望时间复杂度为 \(O(N)\),《算法导论》里给出了具体的复杂度分析;空间复杂度为 \(O(logN)\),即递归栈所使用的额外空间。
注意:选择切分点对于极端情况下程序的计算时间影响很大。如下图,对于leetcode给的测试样例,不使用随机操作程序执行时间会增加5倍以上。
技术图片

解法三:堆排序

堆排序有两种思路:

  1. 使用最大堆进行原地排序,每次从堆中删去堆顶元素(堆中最大元素)并重新调整堆使其有序,在删去 k-1 个元素并堆有序后,堆顶元素就是所求元素。
  2. 建立大小为 k 的最小堆,接下来每次将一个元素和堆顶元素比较,如果大于堆顶元素,则替换堆顶元素并调整使堆有序。遍历完所有元素后堆顶就是所求元素。

下面是思路一的实现,自己在数组原地实现了最大堆。也可以借助java的PriorityQueue类实现堆。

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int heapSize = nums.length;
        for(int i = heapSize/2; i >= 0; i--){
            adjust(nums, i, heapSize);  // 通过调整使得初始堆有序
        }

        while(--heapSize > nums.length - k){
            swap(nums, 0, heapSize);
            adjust(nums, 0, heapSize);
        }
        return nums[heapSize + 1];
    }

    private void adjust(int[] nums, int i, int size){
        while(2 * i + 1  arr[j])   break;
            swap(nums, i, j);
            i = j;
        }
    }

    private void swap(int[] nums, int i, int j){
        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
    }
}

复杂度:使用思路一原地排序,空间复杂度为 \(O(1)\);时间复杂度为 \(O(NlogN)\),其中构建堆的时间复杂度为 \(O(N)\),删除k个元素的时间代价为 \(O(klogN)\),渐进时间复杂度为 \(O(NlogN)\)。使用思路二的最小堆,时间复杂度为 \(O(Nlog(k))\),如果不使用递归不考虑递归栈的空间,空间复杂度为 \(O(k)\)

!!!解法二快速排序和解法三堆排序是TopK问题推荐的解法,一定要掌握!

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

标签:部分   渐进   最小堆   mamicode   ==   top   通过   最小   rand   

原文地址:https://www.cnblogs.com/rezero/p/13222211.html

上一篇:JAVA并行程序基础1

下一篇:java 日期


评论


亲,登录后才可以留言!