【问题标题】:Sample an index of a maximal number in an array, with a probability of 1/(number of maximal numbers)对数组中最大数的索引进行采样,概率为 1/(最大数的个数)
【发布时间】:2014-03-10 03:49:58
【问题描述】:

这是我最近遇到的面试问题之一。程序返回数组中最大数的索引[注意:数组可能包含也可能不包含最大数的多个副本],使得每个索引(包含最大数)具有最大数的 1/no 的概率被退回。

例子:

  • [-1 3 2 3 3],每个位置[1,3,4]都有1/3的概率被返回(三个3)
  • [2 4 6 6 3 1 6 6],[2,3,6,7]中的每一个都有1/4的概率被返回(对应6s的位置)。

首先,我给出了 O(n) 时间和 O(n) 空间算法,在该算法中我收集了一组最大索引,然后从该集合中返回一个随机数。但他要求 O(n) 时间和 O(1) 复杂度程序,然后我想出了这个。

int find_maxIndex(vector<int> a)
{
     max = a[0];
     max_index = 0;
     count = 0;

     for(i = 1 to a.size())
     {
         if(max < a[i])
         {
             max = a[i];
             count = 0;
         }
         if(max == a[i])
         {
              count++;
              if(rand < 1/count) //rand = a random number in the range of [0,1]
                  max_index = i;
         }
      }
      return max_index;
}

我给了他这个解决方案。但我怀疑这个过程是否会以相等的概率选择最大数的索引之一。希望我很清楚。还有其他方法可以做到这一点吗?

【问题讨论】:

    标签: algorithm


    【解决方案1】:

    您的算法运行良好,您可以通过归纳来证明它。

    也就是说,假设它适用于任何大小为N 的数组,证明它适用于任何大小为N+1 的数组。

    因此,给定一个大小为N+1 的数组,可以将其视为大小为N 的子数组,最后跟一个新元素。通过假设,您的算法统一选择子数组的最大元素之一......然后它的行为如下:

    如果新元素大于子数组的最大值,则返回该元素。这显然是正确的。

    如果新元素小于子数组的最大值,则返回子数组上的算法结果。显然也是正确的。

    唯一有点棘手的部分是当新元素等于子数组的最大元素时。在这种情况下,让子数组中的最大元素数为k。然后,根据假设,您的算法以1/k 的概率选择其中之一。通过以概率k/(k+1) 保持相同元素,您可以根据需要使选择相同元素的总体概率等于1/k * k /(k+1) == 1/(k+1)。您还以相同的概率选择最后一个元素,这样我们就完成了。

    要完成归纳证明,只需验证算法适用于大小为 1 的数组。另外,出于实现质量的目的,请修复它以免在大小为 0 的数组上崩溃:-)

    [更新]

    顺便说一句,这个算法及其证明与Fisher-Yates shuffle密切相关(我一直认为这是“Knuth的洗牌算法”,但维基百科说我落后于时代)。

    【讨论】:

    • 其实这是水库采样
    • 通过“保持相同的元素”,你的意思是从子数组中选择相同的最大索引,但现在在整个数组上?我怀疑它的假设概率1/k * k/(k+1)1/k 似乎是有意义的,因为在具有 k 最大元素的原始子数组上选择该最大索引的概率是有意义的。但是我不明白您是如何获得k/(k+1) 的。通过将这两个概率相乘,后者似乎应该是指在子数组的选择(现在代表k 元素)和新的最后一个最大索引之间进行选择的机会。但是你是如何推导出数学的呢?
    • @onepiece:k/(k+1) 就是 1 - 1/(k+1)。这段代码(在“rand”行)以概率1/(k+1) 执行max_index = i,而对于概率1 - 1/(k+1)(等于k/(k+1))则不执行任何操作。你说得对,我应该把它写出来。
    • 当扫描期间计数实际增加时,我仍然不知道该算法如何正确。例如,如果rand 是 0.5,我希望大致选择我的中间元素。如果有 5 个元素,我希望选择元素号 2(0 索引)。不过,根据代码,比较是(rand &lt; 1/1rand &lt; 1/2rand &lt; 1/3 等)。第一个元素被选中的概率更高(几乎 50%),这导致我认为这并不统一。我错过了什么吗?如果相反,这是一个 2-pass 算法,首先计算 count,我猜这可能是有道理的。
    【解决方案2】:

    这个想法是合理的,但魔鬼在细节中。

    首先,您使用的是什么语言?它可能会有所作为。 C 和 C++ 中的 rand() 将返回一个整数,该整数不可能小于 1/count,除非它返回 0。即使这样,如果1/count 是一个整数除法,那么结果总是0

    您的计数也减 1。当您获得新的最大值时,它以 1 开始,但您会在下一个 if 语句中立即增加它。

    【讨论】:

    • 我只是假设它是伪代码,= 用于赋值和比较,1 to a.size() 等。你说得对,他需要else if 而不是if
    【解决方案3】:

    你拥有的是Reservoir sampling!还有另一种易于理解的解决方案,但需要通过两次。

    int find_maxIndex(vector<int> a){
        int count = 1;
        int maxElement = a[0];
        for(int i = 1; i < a.size(); i++){
            if(a[i] == maxElement){
                count ++;
            } else if(a[i] > maxElement){
                count = 1;
                maxElement = a[i];
            }
        }
        int occurrence = rand() % count + 1;
        int occur = 0;
        for(int i = 0; i < a.size(); i++){
            if(a[i] == maxElement){
                occur++;
                if(occur == occurrence) return i;
            }
        }
    }
    

    算法很简单,首先求最大元素在第一遍中出现的次数。并选择一个随机事件并返回该事件的索引。虽然需要两遍,但很容易理解。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-22
      • 2021-06-16
      • 2021-09-03
      • 2022-01-12
      • 2017-10-20
      • 1970-01-01
      相关资源
      最近更新 更多