给定两个整数N和n(N> = n> 0),如何生成长度= n的[0,N]的随机选择(不重复!)? 例如。给定N = 5,n = 3个可能的解是(3,0,2)或(2,4,1)等。
有一个限制阻止使用天真的方法:内存使用必须是O(n),而不是O(N)。
/ * 在天真的方法下,我的意思是使用size = N的临时数组,它最初按顺序用数字0..N-1填充。从该数组中随机选择所需的n个项目。 * /
答案 0 :(得分:4)
遍历从0到N的所有数字m,决定是否在遇到的集合中包含m。您需要根据已处理的数字更新包含下一个数字的概率。
让我们将这个想法应用于给定的例子,其中n = 3且N = 5。首先考虑m = 0。剩下3个数字,有5种可能性,所以0在集合中的概率为3/5。使用随机数生成器决定是否包含该数字。现在考虑m = 1。如果你在集合中包含0,那么你剩下2个数字和4种可能性,所以它应该包含概率2/4,但是如果不包括0,你有3个数字和4种可能性因此应该包括1概率为3/4。这将继续,直到所需的3个数字包含在集合中。
这是Python中的一个实现:
from __future__ import division
import random
def rand_set(n, N):
nums_included=set()
for m in range(N):
prob = (n-len(nums_included)) / (N-m)
if random.random() < prob:
nums_included.add(m)
return nums_included
你可以(并且可能应该)添加一个测试,看看你的集合中有足够的数字,并尽早摆脱循环。
数字存储在一个集合中,其大小从0到n不等,因此使用的存储空间为O(n)
。其他所有东西都使用恒定的空间,所以整体O(n)
。
编辑实际上,你可以采用这种方法更进一步,以便它需要恒定的空间。在Python中,只需根据上述内容创建一个生成器:
def rand_set_iter(n, N):
num_remaining = n
m = 0
while num_remaining > 0:
prob = num_remaining / (N-m)
if random.random() < prob:
num_remaining -= 1
yield m
m += 1
在这里,我继续使用while循环而不是for循环。要存储结果,您当然需要使用O(n)
空格。但是如果你需要做的就是遍历数字,那么生成器版本会在O(1)
中进行迭代。
对于没有生成器的语言,您可以滚动自己的生成器,重复调用函数并更新静态或全局变量。
答案 1 :(得分:2)
简单(但可能非常低效)解决方案只是通过重复选择所需范围内的值来构建列表,并检查您是否已经选择了它。这有一个无限的最长时间,因为你可能总是意外地选择你已经选择的东西。
我对O(n 2 )解决方案模糊不清,在每次迭代中都会选择[0, N - i)
范围内的值,其中i
是您的元素数量已经有了...然后
通过浏览现有的拾取元素将新值映射到范围[0, N)
,如果您发现已经获得的值小于或等于您选择的值,则添加1。你需要仔细考虑,但这实际上是我要研究的方法。
答案 2 :(得分:1)
在python中,这非常简单:
selection = random.shuffle(range(N))[:n]
这是内存中的O(N),因为有效值列表首先生成然后随机播放,因此它无法满足您的要求:(
您可以尝试这样的事情:
N = 5
n = 3
selection = set()
while len(selection) < n:
selection += pick_random_int(0, N)
这基本上是Jon Skeet提出的。这适用于n&lt;&lt; N,但是在n接近N的情况下开始失败。在这种情况下,O(n)和O(N)内存解决方案无论如何都会收敛,你的要求没有实际意义;)
答案 3 :(得分:0)
将间隔[0,N]除以n个间隔。从每个间隔中选择一个随机数,然后随机化结果。问题是在这种情况下,分布并不均匀。