题目:
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
For example,
Given [3,2,1,5,6,4]
and k = 2, return 5.
Note:
You may assume k is always valid, 1 ≤ k ≤ array's length.
链接: http://leetcode.com/problems/kth-largest-element-in-an-array/
题解:
找数组中第k大的元素,可以用heap,Radix sort或者Quick-Select。不过Quick-Select的Time Compleixty只有在Amorized analysis上才是O(n)。下面是使用Java自带的priority queue min-heap来完成的,算是投机取巧了。二刷要补上Radix sort以及Quick-Select
Time Complexity - O(nlogn), Space Complexity - O(k)
public class Solution { public int findKthLargest(int[] nums, int k) { // use min oriented heap, store min value at top of the heap if(nums == null || nums.length == 0) return 0; PriorityQueue<Integer> pq = new PriorityQueue<Integer>(k); for(int i = 0; i < nums.length; i++) { if(pq.size() < k) pq.offer(nums[i]); else { if(nums[i] > pq.peek()) { pq.poll(); // remove min pq.offer(nums[i]); } } } return pq.poll(); } }
二刷:
方法跟1刷一样,建立一个size 为k的min-oriented heap。因为题目要求第k大的元素,所以我们堆里面,遍历完毕后堆顶的元素就是第k大的元素,而其余元素都比这个元素大。三刷要研究quick-select和radit sort
Java:
Time Complexity - O(nlogn), Space Complexity - O(k)
public class Solution { public int findKthLargest(int[] nums, int k) { if (nums == null | nums.length == 0) { return 0; } PriorityQueue<Integer> pq = new PriorityQueue<Integer>(k); for (int i = 0; i < nums.length; i ++) { if (pq.size() < k) { pq.offer(nums[i]); } else if (nums[i] > pq.peek()) { pq.poll(); pq.offer(nums[i]); } } return pq.peek(); } }
三刷:
比较绕的一点是,求kth largest elements,我们使用最小堆,每次堆的size() > k的话就poll(),最后堆顶元素就是第k大的,堆内其他元素都比堆顶元素大。
Java:
Using min-heap:
Time Complexity - O(nlogk), Space Complexity - O(k)
public class Solution { public int findKthLargest(int[] nums, int k) { if (nums == null || nums.length < k) return 0; PriorityQueue<Integer> pq = new PriorityQueue<>(); for (int i : nums) { pq.offer(i); if (pq.size() > k) pq.poll(); } return pq.peek(); } }
Update:
突然明白为什么在面试的时候会有人纠结于pq的size是k还是k + 1了。假如初始化一个size为k的pq,那么在k = 1这种情况下,用下面的代码就有可能算不对结果。面试的时候记得顺着面试官的意思说,不就是聊天嘛,没必要太较真。
public class Solution { public int findKthLargest(int[] nums, int k) { PriorityQueue<Integer> minPQ = new PriorityQueue<>(k + 1); for (int num : nums) { minPQ.offer(num); if (minPQ.size() > k) minPQ.poll(); } return minPQ.peek(); } }
Using quick-select:
速度反而比使用min-heap慢。 这里我们使用quick-select,pick数组的第一个元素作为pivot,然后把大于nums[0]的元素都放到数组前部,小于nums[0]的元素放在数组后部。这样遍历完毕以后nums[k]就是第k大的。
注意这里在开头进行了k--, 把k从1 based转换为0 based,方便写代码和处理边界条件。
Time Complexity - Amortized O(n), worst case O(n2) Space Complexity - O(1)
public class Solution { public int findKthLargest(int[] nums, int k) { if (nums == null || nums.length < k) return 0; k--; int lo = 0, hi = nums.length - 1; while (lo < hi) { int j = partition(nums, lo, hi); if (j < k) lo = j + 1; else if (j > k) hi = j - 1; else return nums[k]; } return nums[lo]; } private int partition(int[] nums, int lo, int hi) { int i = lo, j = hi + 1; while (i < j) { while (nums[++i] > nums[lo]) if (i == hi) break; while (nums[--j] < nums[lo]) if (j == lo) break; if (i >= j) break; swap(nums, i, j); } swap(nums, lo, j); return j; } private void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } }
改进了一下quick-select, 在查找前按照塞神的建议先进行了O(n)的shuffle,速度果然快了不少, 从47ms到达了7ms
public class Solution { public int findKthLargest(int[] nums, int k) { if (nums == null || nums.length < k) return 0; shuffle(nums); k--; int lo = 0, hi = nums.length - 1; while (lo < hi) { int j = partition(nums, lo, hi); if (j < k) lo = j + 1; else if (j > k) hi = j - 1; else return nums[k]; } return nums[lo]; } private void shuffle(int[] nums) { java.util.Random rand = new java.util.Random(System.currentTimeMillis()); for (int i = 0; i < nums.length; i++) { int r = rand.nextInt(i + 1); swap(nums, i, r); } } private int partition(int[] nums, int lo, int hi) { int i = lo, j = hi + 1; while (i < j) { while (nums[++i] > nums[lo]) if (i == hi) break; while (nums[--j] < nums[lo]) if (j == lo) break; if (i >= j) break; swap(nums, i, j); } swap(nums, lo, j); return j; } private void swap(int[] nums, int i, int j) { int tmp = nums[i]; nums[i] = nums[j]; nums[j] = tmp; } }
Reference:
https://leetcode.com/discuss/38336/solutions-partition-priority_queue-multiset-respectively
https://leetcode.com/discuss/45627/ac-clean-quickselect-java-solution-avg-o-n-time
https://leetcode.com/discuss/36913/solutions-java-having-worst-time-complexity-with-explanation
https://leetcode.com/discuss/36991/java-quick-select
https://leetcode.com/discuss/36966/solution-explained
https://leetcode.com/discuss/88064/97%25-2ms-java-quick-select-solution