题目描述
数组中第k大的数是一道十分经典的题目,题目表述可以如下
You are given a set S of n integers in an array and also an integer k
∈ [1, n]. Design an algorithm to find the k-th largest integer of S.
给定 一个数组,返回数组中第k大的元素
215.
数组中的第K个最大元素 - 力扣(LeetCode) (leetcode-cn.com)
剑指 Offer II 076.
数组中的第 k 大的数字 - 力扣(LeetCode) (leetcode-cn.com)
题解
分治算法(快速排序)
首先,一个非常简单的思路,我们直接先将整个数组进行排序,然后就可以直接找到第k大的数字了,这样的时间复杂度是O(nlogn)。但是实际上,我们并不需要将整个数组进行排序,我们在排序的过程中已经可以找到第k大的元素。下面这个算法就是基于快速排序算法找出了第k大的数字(参考《算法导论》以及课上的课件)。
算法思路:首先在数组中随机选取一个下标当做pivot,然后进行partition,将比pivot的值大的放在左侧,比pivot的值小的放在右侧,接下来分三种情况:
pivot==k-1, 我们就已经找到了第k大的元素
pivot<k-1,
我们需要在pivot之后的数字中找到第k-pivot-1大的元素
pivot>k-1, 我们需要在pivot之前的数字中找到第k大的元素
(注:这里是k-1是因为pivot是下标从0开始,k从1开始)
我们还可以对这个算法进行一定的优化,如果说我们随机选取的pivot的位置非常靠左或者靠右,那么这个时候我们在递归之前可能只能排除掉非常少的部分元素,我们可以希望pivot落在数组范围的中间1/3部分,如果不在的话,我们重新选择一个pivot
(实际上感觉这样子提升的效果也不是很好?)下面是代码部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 import java.util.Arrays;import java.util.Collections;import java.util.Random;public class KSelection { Random random = new Random (); public Comparable kSelectionSort (Comparable[] arr, int k) { if (k > arr.length || k <= 0 ) return -1 ; return quickKSelection(arr, 0 , arr.length - 1 , k - 1 ); } private Comparable quickKSelection (Comparable[] arr, int lo, int hi, int k) { if (lo == hi) return arr[hi]; int pivot = partition(arr, lo, hi); if (pivot > k) return quickKSelection(arr, lo, pivot - 1 , k); else if (pivot < k) return quickKSelection(arr, pivot + 1 , hi, k); else return arr[pivot]; } private int partition (Comparable[] arr, int lo, int hi) { int pivot= random.nextInt(hi-lo+1 )+lo; swap(arr, pivot, lo); pivot = lo; int i = lo, j = hi + 1 ; while (true ) { while (arr[++i].compareTo(arr[pivot]) > 0 ) if (i == hi) break ; while (arr[pivot].compareTo(arr[--j]) > 0 ) if (j == lo) break ; if (i >= j) break ; swap(arr, i, j); } swap(arr, pivot, j); pivot = j; if (pivot >= lo + (hi - lo) / 3 && pivot <= hi - (hi - lo) / 3 ) return pivot; else return partition(arr, lo, hi); } private void swap (Comparable[] arr, int i, int j) { Comparable temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } }
时间复杂度:\(f(n)\le
O(n)+f(\lceil2n/3\rceil)\) ,根据Master's theorem,
时间复杂度为O(n)。空间复杂度:函数递归占用栈空间O(logn)。
最大堆
另一个思路也非常容易,就是维护一个大根堆,然后依次将每个元素压入队列中,然后最后将最上面k-1个元素依次弹出,现在堆顶的元素就是数组中第k大的元素。时间复杂度O(nlogn)。代码:
1 2 3 4 5 6 7 8 class Solution { public int findKthLargest (int [] nums, int k) { PriorityQueue<Integer> queue = new PriorityQueue <>(Collections.reverseOrder()); for (int num : nums) queue.add(num); for (int i = 0 ; i < k - 1 ; i++) queue.poll(); return queue.poll(); } }