leetcode *215. 数组中的第K个最大元素(堆排序)

【题目】*215. 数组中的第K个最大元素

剑指 Offer 40. 最小的k个数
*215. 数组中的第K个最大元素
*347. 前 K 个高频元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 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大元素是排序后从后往前数第K个,排序后返回 nums.length-k 位置的元素,当然直接Arrays.sort()也可以

class Solution {
     
    public int findKthLargest(int[] nums, int k) {
     
        //插入排序
        for(int i=1; i<nums.length; i++){
     
            int temp = nums[i]; //暂存要插入的元素
            int j = i;
            while( j>0 && temp<nums[j-1]){
     
                nums[j] = nums[j-1]; //如果要插入的元素小于当前元素,当前元素后移
                j--;
            }
            nums[j] = temp; //找到要插入的位置,插入元素
        }
        return nums[nums.length-k];
    }
}

【解题思路2】优先队列

维护一个大小<=k的有序队列,如果队列中元素的个数<=k时就add,个数>k时把最顶端的元素poll出队,因为是优先队列,所以移除的就是队列中最小的值

class Solution {
     
    public int findKthLargest(int[] nums, int k) {
     
        final PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int val : nums) {
     
            queue.add(val);
            if (queue.size() > k)
                queue.poll();
        }
        return queue.peek();
    }
}

【解题思路3】小顶堆

利用小顶堆的特性(堆顶元素最小),先对前K个数组元素进行"原地"建小顶堆,建完小顶堆后,堆顶的元素最小,正好是这K个元素的第K大元素。
然后遍历剩下的元素 nums[k] ~ nums[len-1],一直维护这个有K个元素的小顶堆,最后的小顶堆就是原数组第K大元素到第最大元素,堆顶就是第K大元素

  • 如果比堆顶元素小,跳过
  • 如果比堆顶元素大,和堆顶元素交换后重新堆化
class Solution {
     
    public int findKthLargest(int[] nums, int k) {
     
        buildHeap(nums, k); //先用前k个元素原地建堆
        //遍历剩下元素,比堆顶小,跳过;比堆顶大,交换后重新堆化
        for(int i=k; i<nums.length; i++){
     
            if(nums[i] < nums[0]){
     
                continue;
            }
            swap(nums, i, 0);
            heapify(nums, k, 0);
        }
        //K个元素的小顶堆的堆顶即是第K大元素
        return nums[0];
    }

    //建堆,从倒数第一个非叶子节点开始堆化,倒数第一个非叶子节点下标为 K/2-1
    public void buildHeap(int[] a, int k){
     
        for(int i=k/2-1; i>=0; i--){
     
            heapify(a, k, i);
        }
    }

	//堆化
	//父节点下标i,左右子节点的下标分别为 2*i+1 和 2*i+2
    public void heapify(int[] a, int k, int i){
     
        int min = i; //临时变量 minPos 用于存储最小值的下标,先假设父节点最小
        while(true){
     
            if(i*2+1<k && a[i*2+1]<a[i]){
      //和左子节点比较
                min = i*2+1;
            }
            if(i*2+2<k && a[i*2+2]<a[min]){
      //和右子节点比较
                min = i*2+2;
            }
            if(min==i){
      //如果minPos没有发生变化,说明父节点已经是最小了,直接跳出
                break;
            }
            swap(a, i, min); //否则交换
            i = min; //父节点下标进行更新,继续堆化
        }
    }

    public void swap(int[] a, int n, int m){
     
        int temp = a[n];
        a[n] = a[m];
        a[m] = temp;
    }
}

【解题思路3】大顶堆

建立一个大根堆,做 k - 1 次删除操作后堆顶元素就是要找的答案

class Solution {
     
    public int findKthLargest(int[] nums, int k) {
     
        int heapSize = nums.length;
        buildMaxHeap(nums, heapSize);
        for (int i = nums.length - 1; i >= nums.length - k + 1; --i) {
     
            swap(nums, 0, i);
            --heapSize;
            maxHeapify(nums, 0, heapSize);
        }
        return nums[0];
    }

    public void buildMaxHeap(int[] a, int heapSize) {
     
        for (int i = heapSize / 2; i >= 0; --i) {
     
            maxHeapify(a, i, heapSize);
        } 
    }

    public void maxHeapify(int[] a, int i, int heapSize) {
     
        int l = i * 2 + 1, r = i * 2 + 2, largest = i;
        if (l < heapSize && a[l] > a[largest]) {
     
            largest = l;
        } 
        if (r < heapSize && a[r] > a[largest]) {
     
            largest = r;
        }
        if (largest != i) {
     
            swap(a, i, largest);
            maxHeapify(a, largest, heapSize);
        }
    }

    public void swap(int[] a, int i, int j) {
     
        int temp = a[i];
        a[i] = a[j];
        a[j] = temp;
    }
}

【解题思路4】快排 (待研究)

第k大元素也是第 N-K 小元素,利用快排的性质不断缩小范围

  • 随机选择一个枢轴,使用划分算法将枢轴放在数组中的合适位置 pos。
  • 将小于枢轴的元素移到左边,大于等于枢轴的元素移到右边。
  • 比较 pos 和 N - k 以决定在哪边继续递归处理。

leetcode *215. 数组中的第K个最大元素(堆排序)_第1张图片

class Solution {
     
    public int findKthLargest(int[] nums, int k) {
     
        int size = nums.length;
        // kth largest is (N - k)th smallest
        return quickselect(nums, 0, size - 1, size - k);
    }

    public void swap(int[] nums, int a, int b) {
     
        int tmp = nums[a];
        nums[a] = nums[b];
        nums[b] = tmp;
    }


    public int partition(int[] nums, int left, int right, int pivot_index) {
     
        int pivot = nums[pivot_index];
        // 1. move pivot to end
        swap(nums, pivot_index, right);
        int store_index = left;

        // 2. move all smaller elements to the left
        for (int i = left; i <= right; i++) {
     
            if (nums[i] < pivot) {
     
            swap(nums, store_index, i);
            store_index++;
            }
        }

        // 3. move pivot to its final place
        swap(nums, store_index, right);

        return store_index;
    }

    public int quickselect(int[] nums, int left, int right, int k_smallest) {
     
        /*
        Returns the k-th smallest element of list within left..right.
        */

        if (left == right) // If the list contains only one element,
            return nums[left];  // return that element

        // select a random pivot_index
        Random random_num = new Random();
        int pivot_index = left + random_num.nextInt(right - left); 

        pivot_index = partition(nums, left, right, pivot_index);

        // the pivot is on (N - k)th smallest position
        if (k_smallest == pivot_index)
            return nums[k_smallest];
        // go left side
        else if (k_smallest < pivot_index)
            return quickselect(nums, left, pivot_index - 1, k_smallest);
        // go right side
        return quickselect(nums, pivot_index + 1, right, k_smallest);
    }

}

你可能感兴趣的:(Leetcode,#,最小/最大的第k个数,#,堆,leetcode,堆排序)