生成从0到N-1的随机整数,该整数不在列表中

时间:2014-06-04 18:42:07

标签: algorithm

您将获得Nint K[]

手头的任务是在0 to N-1之间生成一个相等的概率随机数,这在K中是不存在的。

N严格来说是一个整数>= 0。 并且K.length是&lt; N-1。并0 <= K[i] <= N-1。还假设K已排序,K的每个元素都是唯一的。

您将获得一个函数uniformRand(int M),该函数生成0 to M-1范围内的均匀随机数。并假设此函数的复杂度为O(1)。

示例:

  

N = 7

     

K = {0,1,5}

     

该函数应该返回任意随机数{2,3,4,6}   概率。

我可以得到一个O(N)解决方案:首先生成0到N-K.length之间的随机数。并将如此生成的随机数映射到不在K中的数字。第二步将复杂度设为O(N)。可以在O(log N)中做得更好吗?

4 个答案:

答案 0 :(得分:5)

您可以使用K []中的所有数字介于0和N-1之间且不同的事实。

对于您的示例案例,您生成一个0到3的随机数。假设您得到一个随机数r。现在,您在数组K []上进行二进制搜索。

Initialize i = K.length/2

查找K[i] - i。这将为您提供0到i范围内数组中缺少的数字。

For example K[2] = 5. So 3 elements are missing from K[0] to K[2] (2,3,4)

因此,您可以决定是否必须在数组K的第一部分或下一部分中进行剩余搜索。这是因为您知道r

此搜索会为您提供log(K.length)

的复杂性

编辑:例如,

N = 7

K = {0, 1, 4} // modified the array to clarify the algorithm steps.

the function should return any random number { 2, 3, 5, 6 } with equal probability.
  1. 0N-K.length = random{0-3}之间生成的随机数。假设我们得到3因此我们需要数组K中的第4个缺失数字。

  2. 对数组K[]进行二进制搜索。

    • Initial i = K.length/2 = 1
  3. 现在我们看到K[1] - 1 = 0。因此,i = 1之前没有数字缺失。因此,我们搜索数组的后半部分。

  4. 现在i = 2. K[2] - 2 = 4 - 2 = 2。因此,在2索引之前有i = 2个缺失的数字。但我们需要第4个缺失的元素。所以我们再次必须在数组的后半部分进行搜索。

  5. 现在我们到达一个空数组。我们现在该做什么?如果我们在K[j] & K[j+1]之间找到一个空数组,那么它只是意味着数组K[j]中缺少K[j+1]K之间的所有元素。

  6. 因此,数组中缺少K[2]以上的所有元素,即56。我们需要4th element我们已经丢弃2 elements。因此,我们将选择第6个元素。

答案 1 :(得分:3)

二进制搜索。

基本算法:

(与其他答案不完全相同 - 数字仅在最后生成)

  • K开始。

  • 通过查看当前值及其索引,我们可以确定左侧的可选数字(不在K中的数字)。

    同样,通过加入N,我们可以确定右侧的可选数字。

  • 现在随机向左或向右移动,根据每边可选数字的数量进行加权。

  • 在选定的子阵列中重复,直到子阵列为空。

  • 然后在数组中子阵列之前和之后的数字组成的范围内生成一个随机数。

运行时间为O(log |K|),自|K| < N-1起,O(log N)

数字计数和权重的精确数学可以从下面的例子中得出。

K包含更大范围的扩展:

现在让我们说(为了浓缩目的)K也可以包含N或更大的值。

然后,我们不是从整个K开始,而是从位于min(N, |K|)的子阵列开始,然后从中间开始。

很容易看到N中的K位置(如果存在)将为>= N,因此这个选定的范围包括我们可以生成的任何可能的数字。

从这里开始,我们需要对N进行二分搜索(这会给我们一个点,左边的所有值都是< N,即使找不到N也是如此)(上述算法不处理包含大于K的值的N

然后我们只运行上面的算法,子阵列以最后一个值< N结束。

运行时间为O(log N),或者更具体地说,O(log min(N, |K|))

实施例

N = 10
K = {0, 1, 4, 5, 8}

所以我们从中间开始 - 4

鉴于我们处于索引2,我们知道左边有2个元素,值为4,所以左边有4 - 2 = 2个可选值。

同样,右侧有10 - (4+1) - 2 = 3个可选值。

现在我们左侧的概率为2/(2+3),右侧的概率为3/(2+3)

假设我们走对了,我们的下一个中间值是5

我们处于此子阵列的第一个位置,之前的值为4,因此我们左侧有5 - (4+1) = 0个可选值。

右边有10 - (5+1) - 1 = 3个可选值。

我们不能左转(概率为0)。如果我们走对了,我们的下一个中间值将是8

左侧有2个可选值,右侧有1个。

如果我们离开,我们就会有一个空的子阵列。

然后我们会在58之间生成一个数字,其概率为67

答案 2 :(得分:1)

这可以通过基本解决这个问题来解决:

  

找到不在给定数组中的rth最小数字K,受制于   问题中的条件。

为此考虑 隐式 数组D,由

定义

D[i] = K[i] - i for 0 <= i < L, where L is length of K

我们还设置了D[-1] = 0D[L] = N

我们还定义了K[-1] = 0

注意,我们实际上并不需要构建D。另请注意,D已排序(所有元素均为非负数),因为K[]中的数字是唯一且不断增加的。

现在我们提出以下要求:

  

索赔:要找到不在K []中的最小数字,我们需要找到最合适的r&#39;在D中(发生在由j定义的位置),其中r&#39; D中的最大数字是&lt;河这样的r&#39;存在,因为D [-1] = 0.一旦我们找到这样的r&#39; (和j),我们正在寻找的数字是r-r&#39; + K [j]。

证明:基本上r' and j的定义告诉我们r'中缺少0 to K[j]r个数字,而且0 to K[j+1]个数字超过K[j]+1 to K[j+1]-1 r-r'失踪。因此,K[j] + r-r'中的所有数字都丢失了(而且这些数字的数量至少为(r',j)),我们寻找的数字就在其中,由r给出。

<强>算法:

为了找到D我们需要做的就是r中{{1}}的(修改的)二进制搜索,即使我们找到{{{}},我们也会继续向左移动1}}在数组中。

这是一个O(log K)算法。

答案 3 :(得分:0)

如果您多次运行,可能需要加快您的生成操作:O(log N)时间是不可接受的。

制作一个空数组G。从零开始,在K的值前进时向上计数。如果某个值不在K中,请将其添加到G。如果它在K中,请不要添加它并继续K指针。 (这依赖于K被排序。)

现在你有一个只有可接受数字的数组G

使用随机数生成器从G中选择一个值。

这需要O(N)准备工作,每一代都在O(1)时间内完成。查看N次后,所有操作的摊还时间为O(1)

Python模型:

import random

class PRNG:
    def __init__(self, K,N):
        self.G = []
        kptr   = 0
        for i in range(N):
            if kptr<len(K) and K[kptr]==i:
                kptr+=1
            else:
                self.G.append(i)
    def getRand(self):
        rn = random.randint(0,len(self.G)-1)
        return self.G[rn]

prng=PRNG( [0,1,5], 7)
for i in range(20):
    print prng.getRand()