【Top K 问题】[Leetcode-215] Kth Largest Element in an Array 数组中第K大的数

0. 本文概要

Top K问题在大数据领域非常普遍,而且是在面试中经常被提问的一个问题。

例如: 100w 个数中找出最大的 100 个数。

文章目录

  • **0. 本文概要**
  • **1. 思路**
    • **1.1 堆**
    • **1.2. Quick Select**
  • **2. Kth Largest Element in an Array**
  • **3. 参考文献**

1. 思路

解决Top K问题有两种思路

最直观: 小顶堆(大顶堆 -> 最小100个数);
较高效: Quick Select算法。

下面我们先介绍两种方法, 最后使用Leetcode-215 Kth Largest Element in an Array 进行验证。

1.1 堆

方案 1:在前面的题中,我们已经提到了,用一个含 100 个元素的最小堆完成。复杂度为 O(100w*lg100)。

**小顶堆(min-heap)**有个重要的性质——每个结点的值均不大于其左右孩子结点的值,则堆顶元素即为整个堆的最小值。

JDK中 PriorityQueue实现了数据结构堆,通过指定comparator字段来表示小顶堆大顶堆,默认为null,表示自然序(natural ordering)。

小顶堆解决 Top K 问题的思路:
小顶堆维护当前扫描到的最大100个数,其后每一次的扫描到的元素,若大于堆顶,则入堆,然后删除堆顶;依此往复,直至扫描完所有元素。

Java实现第K大整数代码如下:

public static int findKthLargest(int[] nums, int k) {
		PriorityQueue myQueue = new PriorityQueue<>(k, new Comparator(){
			public int compare(Integer o1, Integer o2){
				return o1 - o2;//小顶堆
//				return o2 - o1;//大顶堆
			}
		});
		for(Integer num: nums){
			//如果堆大小小于K, 直接入堆  或者 元素大于小顶堆的堆顶元素,也入堆
			if(myQueue.size() < k || num > myQueue.peek() ){
				myQueue.offer(num);
			}
			
			if(myQueue.size() > k){
				myQueue.poll();
			}
		}
		
		return myQueue.peek();
	}

1.2. Quick Select

方案 2: 采用快速排序的思想,每次分割之后只考虑比轴大的一部分,知道比轴大的一部分在比 100 多的时候,采用传统排序算法排序,取前 100 个。复杂度为 O(100w*100)。

Quick Select 脱胎于快排(Quick Sort),两个算法的作者都是Hoare,并且思想也非常接近:选取一个基准元素pivot,将数组切分(partition)为两个子数组,比pivot大的扔左子数组,比pivot小的扔右子数组,然后递推地切分子数组。

Quick Select不同于Quick Sort的是其没有对每个子数组做切分,而是对目标子数组做切分。

其次,Quick SelectQuick Sort一样,是一个不稳定的算法;pivot选取直接影响了算法的好坏,worst case下的时间复杂度达到了O(n^2)

下面给出**Quick Sort 快速排序**的Java实现:
感觉我现在都能把快排背下来了。。。。

public static void quickSort(int[] arr, int low, int high){
		int pivot;
		if(low < high){
			pivot = partition(arr, low, high);
			quickSort(arr, pivot+1, high);
			quickSort(arr, low, pivot-1);
		}
	}
	
	public static int partition(int[] arr, int low, int high){
		int pivotKey = arr[low];
		while(low < high){
			while(low < high && arr[high] >= pivotKey)
				high--;
			swap(arr, low, high);
			
			while(low < high && arr[low] <= pivotKey)
				low++;
			swap(arr, low, high);
		}
		return low;
	}
	
	public static void swap(int[] arr, int low, int high){
		int temp = arr[low];
		arr[low] = arr[high];
		arr[high] = temp;
	}

Quick Select的目标是找出第k大元素,所以

  • 若切分后的左子数组的长度 > k,则第k大元素必出现在左子数组中;
  • 若切分后的左子数组的长度 = k-1,则第k大元素为pivot;
  • 若上述两个条件均不满足,则第k大元素必出现在右子数组中。

Quick Select的Java实现:

	public int findKthLargest(int[] nums, int k) {
		return quickSelect(nums, k, 0, nums.length - 1);
	}

	// quick select to find the kth-largest element
	public int quickSelect(int[] arr, int k, int left, int right) {
		if (left == right)
			return arr[right];
		
		//patition方法还是用的 快排中的 partition
		int index = partition(arr, left, right);
		
		if (index - left + 1 > k)
			return quickSelect(arr, k, left, index - 1);
		else if (index - left + 1 == k)
			return arr[index];
		else
			return quickSelect(arr, k - index + left - 1, index + 1, right);

	}

上面给出的代码都是求解第k大元素;若想要得到Top K元素,仅需要将代码做稍微的修改:比如,扫描完成后的小顶堆对应于Top K,Quick Select算法用中间变量保存Top K元素。


2. Kth Largest Element in an Array

Leetcode 215

【Top K 问题】[Leetcode-215] Kth Largest Element in an Array 数组中第K大的数_第1张图片

import java.util.Comparator;
import java.util.PriorityQueue;

class Solution {
    public int findKthLargest(int[] nums, int k) {
          PriorityQueue minQueue = new PriorityQueue<>(k);
          for (int num : nums) {
            if (minQueue.size() < k || num > minQueue.peek())
              minQueue.offer(num);
            if (minQueue.size() > k)
              minQueue.poll();
          }
          return minQueue.peek();
    }
}

3. 参考文献

James Aspnes, QuickSelect.

你可能感兴趣的:(数据结构与算法,刷题)