【问题标题】:Find Kth largest number in single pass without storing the whole array在不存储整个数组的情况下单次查找第 K 个最大数
【发布时间】:2016-09-20 04:28:20
【问题描述】:

我想到的算法是

  • 保留大小为 K 的 MaxHeap
  • 插入每个元素
  • 如果堆已满,则丢弃较小的值
  • 最终Kth max是MaxHeap中较小的一个

这会给我 O(NlogK)。有更好的算法吗?我无法快速选择,因为数组无法存储在内存中。

【问题讨论】:

  • @Henk Holterman:数组不能存储在内存中的要求使其不是该答案的副本。

标签: arrays algorithm selection space-complexity


【解决方案1】:

根据您的内存限制,您可以使用中位数算法的修改版本来解决 O(n) 时间和 O(k) 空间中的问题。

思路如下。在内存中维护一个大小为 2k 的数组。用数组中的前 2k 个元素填充此缓冲区,然后对其运行中位数算法,将最大的 k 个元素放在数组的前半部分,将最小的 k 个元素放在后半部分。然后,丢弃最小的 k 个元素。现在,将接下来的 k 个元素加载到数组的后半部分,使用中位数算法再次将顶部 k 个元素放在左侧,底部 k 个元素放在右侧。如果您在整个数组中迭代此过程 - 用数组中的下一个 k 元素替换缓冲区的后半部分,然后使用中位数的中位数将这些元素的顶部 k 移动到左半部分 - 那么最后你会在数组的左半部分有前 k 个元素。找到其中最小的(在 O(k) 时间内)会给你第 k 个最大的元素。

总体而言,您最终会使用 O(k) 大小的数组对中位数算法进行 O(n / k) 次调用,即 O(n / k) 次调用需要 O( k) 时间,对于 O(n) 的净运行时间。这与最后一步相结合,运行时间为 O(n + k) = O(n)。此外,由于中位数步长的内存使用量为 O(k),并且由于周围有一个大小为 O(k) 的缓冲区,因此它仅使用 O(k) 内存。换句话说,它比最小堆解决方案渐近更快,并且在内存中渐近等效。

希望这会有所帮助!

【讨论】:

  • 我迟到了,但只是想说明一下。在“将下一个 k 元素加载到数组的后半部分”步骤中,您当然应该过滤掉大于数组前半部分中最大元素的任何内容。 (所以不要取下一个 k,取下一个可能足够小的 k。)
【解决方案2】:

这被称为http://en.wikipedia.org/wiki/Selection_algorithm

特别是一种算法是http://en.wikipedia.org/wiki/Selection_algorithm#Linear_general_selection_algorithm_-_Median_of_Medians_algorithm

它是O(N) 时间和O(1) 空间。如果您的数组未排序,我相信它可以就地完成。如果您的阵列存储在外部(硬盘、网络等),您可以利用必须使用的 ~K 字词。如果你的数组是由函数动态生成的,你也会遇到类似的情况。

【讨论】:

  • 这是O(1) 辅助 空间 - 你仍然需要存储原始数组,OP 说他不能这样做。
  • @|\/|ad:我知道; OP 说他无法将 all 存储在内存中。他仍然必须将它存储在某个地方(除非它是动态生成的)。如果他正在使用内存,如果他的数组没有排序,他可以就地执行。如果他使用其他存储,则没有问题。此外,他显然在使用 ~K 记忆单词,如果他愿意,他可以使用这些单词。
【解决方案3】:

稍微修改算法,因为 maxheap 不支持高效的“查找最小”。

  • 将前 K 项插入到 最小堆
  • 对于剩余的项,如果值大于堆 头

    • 弹出头部,插入新项目。
  • 头部是第 K 个最大的项目。

最坏的情况仍然是 O(N lg K) 对于每个项目都大于最后一个 K 的最小的输入。但是对于随机分布的输入,您只需要在较小的输入上进行堆操作项目的百分比。

【讨论】:

    【解决方案4】:

    运行此代码 - 它运行良好,无需触及元素位置或排序。

    public class nth {
    
        /**
         * @param args
         */
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            calc.getit(2);
        }
    
    }
    class calc{
        static int arr[] = { 1, 23, 47, 81, 92, 87, 52, 48, 56, 66, 65, 76, 71, 85,
            49, 53, 56, 61, 65, 84 };
    
        static void getit(int k){
            int i,j=0;
            for(i=0;i<arr.length;i++){
                int c=0;
                for(j=0;j<arr.length;j++){
                    if(arr[i]>arr[j]){
                        c++;
                    }
                }
                if(c==k-1){
                    System.out.println(arr[i]);
                }
            }
        }
    }
    

    【讨论】:

      【解决方案5】:

      我有一个使用 PriorityQueue 的实现。试试这个:

      import java.util.PriorityQueue;
      
      public class FindKthLargestElementWithHeap {
      
          /**
           * We can use a min heap to solve this problem. 
           * The heap stores the top k elements.
           * Whenever the size is greater than k, delete the min.
           * Time complexity is O(nlog(k)).
           * Space complexity is O(k) for storing the top k numbers.
           */
          public static int findKthLargest(int[] nums, int k) {
              PriorityQueue<Integer> q = new PriorityQueue<>(k);
              for(int i: nums){
                  q.offer(i);
      
                  if(q.size()>k){
                      q.poll();
                  }
              }
      
              return q.peek();
          }
      
          public static void main(String args[]) {
              int[] nums = {5,8,6,97,12,3,5,6,4,2,3,};
              //Return the second largest number
              System.out.println(findKthLargest(nums,2));
          }
      
      }
      

      欲了解更多信息,请访问:https://github.com/m-vahidalizadeh/foundations/blob/master/src/data_structures/FindKthLargestElementWithHeap.java

      【讨论】:

        猜你喜欢
        • 2011-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-04
        相关资源
        最近更新 更多