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问题推荐的解法,一定要掌握!

  • 相关阅读:
    jquery图片播放弹出插件Fancybox
    D3js-API介绍【英】
    ZOJ 3156 Taxi (二分匹配+二分查找)
    linux权限之su和sudo的差别
    CareerCup之1.6 Rotate Image
    [oracle]pl/sql --分页过程demo
    已迁移到http://www.coffin5257.com
    C# 之 集合ArrayList
    Java 序列化Serializable具体解释(附具体样例)
    Android 短信验证码控件
  • 原文地址:https://www.cnblogs.com/rezero/p/13222211.html
Copyright © 2011-2022 走看看