查找给定范围内的随机数,并排除某些可能的数字

时间:2013-09-13 19:30:36

标签: algorithm random language-agnostic

假设您在范围内获得了一个范围和一些数字(例外)。现在,您需要生成范围内的随机数,但除外例外

例如,如果range = [1..5]和exceptions = {1,3,5},你应该以相同的概率生成2或4。

我应该使用什么逻辑来解决这个问题?

7 个答案:

答案 0 :(得分:5)

如果您没有任何约束,我想这是最简单的方法:创建一个包含有效值a[0]...a[m]的数组。返回a[rand(0,...,m)]

如果您不想创建辅助数组,但可以计算原始范围中的异常e和元素n的数量,则只需生成一个随机数{{ 1}},然后找到带有计数器的有效元素,该计数器不会在异常上打勾,并在等于r=rand(0 ... n-e)时停止。

答案 1 :(得分:4)

取决于案件的具体情况。对于你的具体例子,如果Uniform(0,1)低于1/2,我将返回2,否则返回4。类似地,如果我看到诸如“例外是奇数”之类的模式,我将生成一半范围的值并加倍。但是,一般情况下,我会在范围内生成数字,检查它们是否在异常集中,并拒绝并重新尝试它们 - 这是一种被称为接受/拒绝的技术,原因很明显。有多种技术可以使异常列表检查有效,具体取决于它有多大以及它可能具有哪些模式。

答案 2 :(得分:2)

在Python中,解决方案非常简单(给出您的示例):

import random 
rng = set(range(1, 6))
ex = {1, 3, 5}
random.choice(list(rng-ex))

要优化解决方案,需要知道范围有多长,以及有多少例外。如果异常的数量非常少,则可以从该范围生成一个数字,并检查它是否不是例外。如果异常的数量占主导地位,那么将剩余的数字收集到一个数组中并生成用于获取非异常的随机索引可能是有意义的。

在这个答案中,我假设知道如何从一个范围中得到一个整数随机数。

答案 3 :(得分:2)

让我们假设,为了简单起见,数组从1开始编制索引,并且您的范围从1k。当然,如果不是这种情况,您总是可以将结果移动一个常量。我们将调用异常数组ex_array,假设我们有c个异常。这些需要排序,这将在一段时间内变得相当重要。

现在,您只能使用k-e个有用的数字,因此找到1k-e范围内的随机数会很有意义。假设我们最终得到了数字r。现在,我们只需要在您的数组中找到r-th 有效号码。简单?没那么多。请记住,您可以从不以线性方式简单地遍历任何阵列,因为当您拥有大量数字时,这可能会减慢您的实施速度。例如,你进行某种二进制搜索,以提出足够快的算法。

所以让我们尝试更好的东西吧。如果您没有例外,r-th号码名义会在原始数组中的索引r撒谎。索引r的数字当然是r,因为您的范围和数组索引从1开始。但是,1r之间存在大量无效数字,并且您希望以某种方式转到r-th 有效号码。因此,让我们对异常数组ex_array进行二进制搜索,以查找等于或小于 r的无效数字的数量,因为我们有很多这些无效数字无效位于1r之间的数字。如果这个数字是0,我们就完成了,但如果不是,我们还有更多工作要做。

假设您在二进制搜索后发现n1之间存在r个无效数字。让我们将数组中的 n索引推送到索引r+n,并找到位于1r+n之间的无效数字的数量,使用二进制搜索以查找ex_array中有多少元素小于或等于r+n。如果此号码正好是n 不再 ,则会遇到无效号码,并且您已点击r-th有效号码。否则,请再次重复,这次是索引r+n',其中n'1r+n之间的随机数。

重复直到找到没有找到多余的异常的阶段。这里重要的是你永远不必以线性方式遍历任何阵列。您应该优化二进制搜索,使它们不总是从索引0开始。假设您知道1r之间有n个随机数。您可以从1中与n对应的索引之后的一个索引启动它,而不是从ex_array开始下一个二进制搜索。

最差的情况下,您将对ex_array中的每个元素进行二进制搜索,这意味着您将执行c二进制搜索,第一个从索引1,索引2中的下一个,依此类推,这使您的时间复杂度为O(log(n!))。现在,Stirling's approximation告诉我们O(ln(x!)) = O(xln(x)),因此,如果c小到O(cln(c)) < O(k),则使用上述算法才有意义,因为您可以实现O(k)复杂度首先使用从数组中提取有效元素的简单方法。

答案 4 :(得分:1)

这是另一种方法......继续生成随机数,直到你得到一个未被排除的数据。

假设您的期望范围是[0,100],不包括25,50和75。

将排除的值放在哈希表或位阵列中以便快速查找。

int randNum = rand(0,100);

while( excludedValues.contains(randNum) )
{
    randNum = rand(0,100);
}

复杂性分析更加困难,因为可能的兰德(0,100)每次都可以返回25,50或75。然而,这是不太可能的(假设一个随机数生成器),即使排除了一半的范围。

在上述情况下,我们仅为原始值的3/100重新生成随机值。

因此有3%的时间重新生成一次。在这3%中,只有3%需要再生等。

答案 5 :(得分:1)

假设初始范围是[1,n],并且排除集的大小是x。首先生成从[1,n-x]到数字[1,n]的地图,不包括排除集中的数字。由于两侧的数字相等,所以这与1-1的映射。在问题中给出的示例中,映射如下 - {1-> 2,2-> 4}。

另一个例子假设列表是[1,10]并且排除列表是[2,5,8,9]则映射是{1-> 1,2-> 3,3-> 4 ,4-> 6,5-> 7,6-> 10}。可以在最坏情况下的时间复杂度O(nlogn)中创建此映射。

现在在[1,n-x]之间生成一个随机数,并使用映射将其映射到相应的数字。地图外观可以在O(logn)中完成。

答案 6 :(得分:0)

如果您有调查员或设置操作,您可以以多种方式执行此操作。例如,使用Linq:

void Main()
{
    var exceptions = new[] { 1,3,5 };
    RandomSequence(1,5).Where(n=>!exceptions.Contains(n))
                       .Take(10)
                       .Select(Console.WriteLine);
}

static Random r = new Random();

IEnumerable<int> RandomSequence(int min, int max)
{
    yield return r.Next(min, max+1);
}

我想感谢一些现已删除的评论:

  • 这个程序可能永远不会结束(仅在理论上),因为可以是一个永远不包含有效值的序列。有道理。我认为这可以向面试官解释,但我相信我的榜样对于上下文来说已经足够了。

  • 分配是公平的,因为每个元素都有相同的可能性。

  • 回答这种方式的优势就是你对现代“功能风格”编程的理解,这可能会让采访者感兴趣。

  • 其他答案也是正确的。这是对问题的不同看法。