我接受了Facebook的采访,他们问了我这个问题。
假设您有一个带有N个不同值的无序数组
$ input = [3,6,2,8,9,4,5]
实现一个找到第K个最大值的函数。
EG:如果K = 0,则返回9.如果K = 1,则返回8.
我做的是这种方法。
private static int getMax(Integer[] input, int k)
{
List<Integer> list = Arrays.asList(input);
Set<Integer> set = new TreeSet<Integer>(list);
list = new ArrayList<Integer>(set);
int value = (list.size() - 1) - k;
return list.get(value);
}
我刚测试过,这个方法可以根据问题正常运行。然而,受访者说,in order to make your life complex! lets assume that your array contains millions of numbers then your listing becomes too slow. What you do in this case?
提示,他建议使用min heap
。根据我的知识,堆的每个子值不应超过根值。所以,在这种情况下,如果我们假设3是root,那么6是它的子,它的值比root的值更大。我可能错了,但你的想法是什么,基于min heap
的实现是什么?
答案 0 :(得分:19)
他实际上已经给了你完整的答案。不只是一个提示。
您的理解基于max heap
。不是min heap
。它的运作方式不言自明。
在 min heap 中,root的最小(小于它的子级)值。
因此,您需要的是,遍历数组并在 min heap 中填充K
个元素。
一旦完成,堆就会自动包含根目录中的最低值。
现在,对于从数组中读取的每个(下一个)元素, - &GT;检查该值是否大于最小堆的根。 - &GT;如果是,请从最小堆中删除root,然后将值添加到其中。
遍历整个数组后, min heap 的根将自动包含k
个最大元素。
堆中的所有其他元素(准确地说是k-1个元素)将大于k
。
答案 1 :(得分:5)
以下是在java中使用 PriorityQueue 实现 Min Heap 。 复杂性: n * log k
。
import java.util.PriorityQueue;
public class LargestK {
private static Integer largestK(Integer array[], int k) {
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(k+1);
int i = 0;
while (i<=k) {
queue.add(array[i]);
i++;
}
for (; i<array.length; i++) {
Integer value = queue.peek();
if (array[i] > value) {
queue.poll();
queue.add(array[i]);
}
}
return queue.peek();
}
public static void main(String[] args) {
Integer array[] = new Integer[] {3,6,2,8,9,4,5};
System.out.println(largestK(array, 3));
}
}
输出:5
数组上的代码循环 O(n)
。 PriorityQueue(Min Heap)的大小为k,因此任何操作都是 log k
。在最糟糕的情况下,所有数字都按 ASC 排序,复杂性为 n*log k
,因为对于每个元素,您需要删除堆顶部和插入新元素。
答案 2 :(得分:2)
修改:检查此answer是否有O(n)解决方案。
您也可以使用PriorityQueue来解决此问题:
public int findKthLargest(int[] nums, int k) {
int p = 0;
int numElements = nums.length;
// create priority queue where all the elements of nums will be stored
PriorityQueue<Integer> pq = new PriorityQueue<Integer>();
// place all the elements of the array to this priority queue
for (int n : nums){
pq.add(n);
}
// extract the kth largest element
while (numElements-k+1 > 0){
p = pq.poll();
k++;
}
return p;
}
来自Java doc:
实施说明:此实施提供 O(log(n))时间 排队和出队方法(
offer
,poll
,remove()
和add
);remove(Object)
和contains(Object)
的线性时间 方法;和检索方法的恒定时间(peek
,element
和size
)。
for循环运行n
次,上述算法的复杂度为O(nlogn)
。
答案 3 :(得分:0)
如果数组/流中的元素数量未知,则基于堆的解决方案是完美的。但是,如果它们是有限的但仍然需要线性时间内的优化解决方案。
我们可以使用快速选择,讨论here。
数组= [3,6,2,8,9,4,5]
让我们选择枢轴作为第一个元素:
pivot = 3(第0个索引),
现在以这样的方式对数组进行分区:小于或等于的所有元素都在左侧,而在右侧的数字大于3。喜欢它在快速排序中完成(在我的blog上讨论)。
首次通过后 - [2, 3 ,6,8,9,4,5]
枢轴索引是1(即它是第二个最低元素)。现在再次应用相同的过程。
现在选择6,前一个支点后的指数值 - [2,3,4,5, 6 ,8,9]
所以现在6在适当的地方。
继续检查是否找到了合适的数字(每次迭代中最大的第k个或第k个)。如果发现你已经完成,那么继续。
答案 4 :(得分:0)
k
的常量值的一种方法是使用部分插入排序。
(这假定了不同的值,但也可以很容易地改变以使用重复值)
last_min = -inf
output = []
for i in (0..k)
min = +inf
for value in input_array
if value < min and value > last_min
min = value
output[i] = min
print output[k-1]
(这是伪代码,但应该很容易在Java中实现)。
整体复杂度为O(n*k)
,这意味着当且仅当k
保持不变或已知log(n)
时,它才能正常运作。
从好的方面来说,这是一个非常简单的解决方案。在负面,它没有堆解决方案那么有效