package com.freetymekiyan.algorithms.level.medium;
import org.junit.Test;
import java.util.PriorityQueue;
/**
* 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.
* <p>
* For example,
* Given [3,2,1,5,6,4] and k = 2, return 5.
* <p>
* Note:
* You may assume k is always valid, 1 ≤ k ≤ array's length.
* <p>
* Credits:
* Special thanks to @mithmatt for adding this problem and creating all test cases.
* <p>
* Company Tags: Facebook, Amazon, Microsoft, Apple, Bloomberg, Pocket Gems
* Tags: Heap, Divide and Conquer
* Similar Problems: (M) Wiggle Sort II, (M) Top K Frequent Elements, (E) Third Maximum Number
*/
public class KthLargestElementInAnArray {
/**
* Heap. Priority queue. O(nlogk) Time, O(k) Space.
* Create a min heap as a window.
* For each number in the array, add it to the heap.
* If heap size > k, poll from the heap.
* Return the top of the heap at the end.
*/
public int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> pq = new PriorityQueue<>();
for (int n : nums) {
pq.offer(n);
if (pq.size() > k) {
pq.poll();
}
}
return pq.peek();
}
/**
* QuickSelect. Binary Search. Partition.
* Use partition algorithm in Quick Sort.
* Binary search the given array.
* Each round get the ranking r returned from partition algorithm.
* If r > k - 1, go to the left.
* If r < k - 1, go to the right.
* If r = k - 1, kth largest number found.
*/
public int findKthLargest2(int[] nums, int k) {
int lo = 0;
int hi = nums.length - 1;
while (lo < hi) { // Here, lo must be smaller than hi as partition's input.
int r = partition(nums, lo, hi);
if (r < k - 1) { // k - 1, not k, because the ranking is 0-based.
lo = r + 1;
} else if (r > k - 1) {
hi = r - 1;
} else {
break;
}
}
return nums[k - 1];
}
/**
* Partition algorithm in quick sort. O(n).
* Find ranking: how large is a[hi] in the array.
* Given an array and the range to be partitioned.
* Initialize the last element as the pivot.
* Initialize two pointers, i from lower bound, j from higher bound - 1.
* Move i to the next number <= a[hi], which will be thrown to the end of the array.
* Move j to the next number > a[hi], which should be at the front of the array.
* Check whether i and j overlap.
* If doesn't, swap a[i] and a[j].
* If overlap, break, and swap a[i] and a[hi] to move pivot to the position it belongs.
* i is the final position since all elements before i are > a[hi].
*/
private int partition(int[] a, int lo, int hi) {
int pivot = a[hi];
int i = lo;
int j = hi - 1;
while (true) {
while (i < hi && a[i] > pivot) {
i++;
}
while (j >= lo && a[j] <= pivot) {
j--;
}
if (i >= j) {
break;
}
swap(a, i, j);
}
swap(a, hi, i);
return i;
}
private void swap(int[] a, int i, int j) {
final int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
@Test
public void testExamples() {
int res = findKthLargest(new int[]{-1, 2, 0}, 2);
System.out.println(res);
res = findKthLargest2(new int[]{2, 1}, 1);
System.out.println(res);
res = findKthLargest2(new int[]{1}, 1);
System.out.println(res);
}
}