Java,从数组中查找Kth最大值

时间:2015-09-01 05:19:42

标签: java arrays algorithm min-heap

我接受了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的实现是什么?

5 个答案:

答案 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))时间   排队和出队方法(offerpollremove()和   add); remove(Object)contains(Object)的线性时间   方法;和检索方法的恒定时间(peek,   elementsize)。

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)时,它才能正常运作。

从好的方面来说,这是一个非常简单的解决方案。在负面,它没有堆解决方案那么有效