【问题标题】:Java, Finding Kth largest value from the array [duplicate]Java,从数组中找到第K个最大值[重复]
【发布时间】:2015-11-26 05:50:53
【问题描述】:

我接受了 Facebook 的采访,他们问了我这个问题。

假设你有一个具有 N 个不同值的无序数组

$输入 = [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 是根,那么 6 是它的子节点,并且它的值大于根的值。我可能错了,但是您的想法以及基于min heap 的实现是什么?

【问题讨论】:

  • 你为什么不向面试官索要代码示例?
  • 在最小堆中,每个节点都小于或等于它的两个子节点。因此,根将是2 而不是3。一种可能的布局是树 2 -&gt; [3,4], 3 -&gt; [5,6], 4 -&gt; [8,9]
  • 为什么要转换成 TreeSet 并返回,而不仅仅是调用 Collections.sort?
  • @immibis 哦,是的,我不熟悉 :( 我想从列表中删除所有重复项并按升序对其进行排序。我不确定 Collections.sort 是否删除了重复项!

标签: java arrays algorithm min-heap


【解决方案1】:

编辑:检查此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;
    }

来自Javadoc

实施说明:此实施提供 O(log(n)) 时间 入队和出队方法(offerpollremove()add); remove(Object)contains(Object) 的线性时间 方法;检索方法的固定时间(peek, elementsize)。

for循环运行n次,上述算法的复杂度为O(nlogn)

【讨论】:

  • 感谢@akhil,但如果您假设num 数组包含一百万个项目,我认为它仍然存在 O(n) 问题。您的 ForWhile 循环各为 O(n)。或者我所有的知识都搞砸了:(
  • 另外,由于您将整个数组复制到队列中,因此您的空间复杂度过高。
  • 我正在寻找的一个见解:因为我正在寻找第 K 个最大值,如果我的堆/优先级队列中已经有 K 个元素,并且一个数字 A 进来了小于其中最小的——那么 A 肯定不会是最大的 K 之一,因此甚至不应该放入数据结构中。换句话说,数据结构不应包含超过 K 个元素。这样,即使你有数千万的价值——甚至数十亿! -- 并且它们正在流入(因为它们并不都适合内存),如果 K 足够小,您仍然可以解决问题。
  • 您的 while 循环是 O(n),其中每个 poll() 是 O(log(n))。所以总补偿是O(nlog(n))
  • @immibis 在我看来,while 循环的意思似乎很清楚,正如对确实在 while 循环中的 polls 的引用所证明的那样
【解决方案2】:

他实际上已经给了你全部的答案。不仅仅是提示。

而你的理解是基于max heap。不是min heap。它的工作原理是不言自明的。

最小堆中,根具有最小(小于其子堆)值。

所以,您需要的是遍历数组并在 min heap 中填充 K 元素。 一旦完成,堆会自动包含根处的最低值。

现在,对于您从数组中读取的每个 (next) 元素, -> 检查该值是否大于最小堆的根。 -> 如果是,则从最小堆中删除根,并将值添加到其中。

遍历整个数组后,最小堆的根会自动包含kth 最大的元素。

堆中的所有其他元素(准确地说是k-1个元素)都将大于k

【讨论】:

  • 谢谢,我发现我的问题出在哪里。将尝试找到正确的实施方式。
  • @Codebender 如果您能解释一下问题中给出的示例,那就太好了。我想学它。谢谢
  • @UmaKanth,你想要一个堆实现的例子(谷歌有很多例子)还是解释一下算法的其他部分?
  • @UmaKanth 我已经提供了问题的实现。请检查答案。
【解决方案3】:

这是 Min Heap 在 java 中使用 PriorityQueue 的实现。 复杂性: 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,因为对于每个元素,您需要移除堆顶并插入新的元素。

【讨论】:

  • @njzk2 复杂度是n*log k,而不是n*log n。代码循环遍历数组 O(n)PriorityQueue(最小堆)的大小是 k,所以任何操作都是 log k。在最坏的情况下,所有数字都按 ASC 排序,复杂度为n*log k。因为对于每个元素,您需要删除堆顶部并插入新元素。
  • 对,我不认为queue.poll(); 确保队列的大小始终为k
【解决方案4】:

如果数组/流中的元素数量未知,则基于堆的解决方案是完美的。但是,如果它们是有限的,但您仍然需要线性时间的优化解决方案。

我们可以使用快速选择,讨论过here

数组 = [3,6,2,8,9,4,5]

让我们选择枢轴作为第一个元素:

pivot = 3(在第 0 个索引处),

现在以这样一种方式对数组进行分区,即所有小于或等于的元素都位于左侧,大于 3 的数字位于右侧。就像它在快速排序中完成的一样(在我的blog 上讨论过)。

所以在第一遍之后 - [2,3,6,8,9,4,5]

pivot index 为 1(即它是第二低的元素)。现在再次应用相同的过程。

现在选择 6,索引处的值在上一个枢轴之后 - [2,3,4,5,6,8,9]

所以现在 6 是在正确的位置。

继续检查您是否找到了合适的数字(每次迭代中第 k 个最大或第 k 个最小)。如果找到你就完成了,否则继续。

【讨论】:

    【解决方案5】:

    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) 时,它才能很好地工作。

    从好的方面来说,这是一个非常简单的解决方案。不利的一面是,它不如堆解决方案高效

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-03-20
      • 1970-01-01
      • 2020-02-09
      • 2021-01-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多