zoukankan      html  css  js  c++  java
  • LeetCode.215-数组中的第K个最大元素

    题目

    在未排序的数组中找到第 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 < hi){
                while(lo < hi && nums[hi] <= tmp){
                    hi--;
                }
                nums[lo] = nums[hi];
                while(lo < hi && nums[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 < size){
                int j = 2 * i + 1;
                if(j + 1 < size && nums[j] < nums[j + 1]){  // 左右孩子中值最大的索引
                    j++;
                }
                if(arr[i] > 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】Binary Search Tree Iterator
    【leetcode】Palindrome Partitioning II
    【leetcode】Best Time to Buy and Sell Stock III
    【leetcode】Best Time to Buy and Sell Stock II
    【leetcode】Longest Consecutive Sequence
    【leetcode】Factorial Trailing Zeroes
    【leetcode】Simplify Path
    【leetcode】Generate Parentheses
    【leetcode】Combination Sum II
    【leetcode】Combination Sum
  • 原文地址:https://www.cnblogs.com/rezero/p/13222211.html
Copyright © 2011-2022 走看看