【问题标题】:Find random numbers in a given range with certain possible numbers excluded查找给定范围内的随机数,排除某些可能的数字
【发布时间】:2013-09-13 19:30:35
【问题描述】:

假设给你一个范围和范围内的几个数字(例外)。现在你需要在给定的exceptions范围内生成一个随机数except

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

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

【问题讨论】:

  • 这个问题如何属于“太宽泛”的关闭原因?考虑到问题的参数,不可能有一个可接受的答案,这意味着它不适合 SO。
  • 您可以通过寻找算法而不是实现来使其不可知(可能是最好的选择 - 请参阅我的编辑),或者您可以通过指定语言使其具体化,但要求任何阅读的人提供以他们喜欢的任何语言实现并不真正适合我们在这里所做的。现在,如果你想把精力放在make a game out of it...

标签: algorithm random language-agnostic


【解决方案1】:

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

如果不想创建辅助数组,但是可以统计异常e和元素n在原始范围内的个数,可以简单生成一个随机数r=rand(0 ... n-e),然后找到具有不会在异常上打勾的计数器的有效元素,并在等于 r 时停止。

【讨论】:

    【解决方案2】:

    取决于案件的具体情况。对于您的具体示例,如果 Uniform(0,1) 低于 1/2,我将返回 2,否则返回 4。同样,如果我看到诸如“例外是奇数”之类的模式,我会生成范围的一半和两倍的值。不过,一般来说,我会生成该范围内的数字,检查它们是否在异常集中,如果是则拒绝并重试 - 出于显而易见的原因,这种技术称为接受/拒绝。有多种技术可以提高异常列表检查的效率,具体取决于它的大小和可能具有的模式。

    【讨论】:

      【解决方案3】:

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

      现在,您只有k-e 有用的数字可供使用,因此在1k-e 范围内找到一个随机数将是有意义的。假设我们最终得到号码r。现在,我们只需要在您的数组中找到r-th valid 数字。简单的?没那么多。请记住,您可以永远简单地以线性方式遍历您的任何数组,因为当您有很多数字时,这确实会减慢您的实现速度。你已经做了一些二分搜索,比如说,想出一个足够快的算法。

      所以让我们尝试一些更好的东西。 r-th 数字将名义上在您的原始数组中的索引r 处撒谎,如果您没有例外。索引r 处的数字当然是r,因为您的范围和数组索引从1 开始。但是,您在1r 之间有一堆无效数字,并且您想以某种方式获得r-th 有效 数字。因此,让我们对异常数组ex_array 进行二分搜索,以找出有多少无效数字等于或小于r,因为我们有很多无效数字位于@ 之间987654342@ 和r。如果这个数字是0,我们就完成了,但如果不是,我们还有一些工作要做。

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

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

      最糟糕的情况下,您将对ex_array 中的每个元素进行二进制搜索,这意味着您将执行c 二进制搜索,第一个从索引1 开始,下一个来自索引2,依此类推,时间复杂度为O(log(n!))。现在,Stirling's approximation 告诉我们O(ln(x!)) = O(xln(x)),所以使用上面的算法只有在c 足够小到O(cln(c)) < O(k) 时才有意义,因为您可以使用从您的数组优先。

      【讨论】:

        【解决方案4】:

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

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

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

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

        【讨论】:

          【解决方案5】:

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

          假设您想要的范围是 [0,100),不包括 25,50 和 75。

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

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

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

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

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

          【讨论】:

          • 此解决方案不是确定性的。无论机会多么渺茫,你都可能永远奔跑。
          • @piyush 从技术上讲,它甚至比这更糟糕,因为它使用的是 PRNG 而不是 RNG,这使得它更有可能永远运行下去。但是,假设一个真正的 RNG,并且排除了一半的范围,那么持续超过 100 次迭代的机会是 2^100 分之一。如果排除 99% 的范围,则在 (99/100)^100 中减少到 1,这大约是时间的 1/3,但在 1000 次迭代后只有 0.00004% 的机会。
          • 这是一个完全确定的解决方案 - stackoverflow.com/questions/18793852/…
          • @piyush 使用最适合手头任务的任何方法...如果您想从 1...1,000,000,000 中选择并且只排除 {42},您可能不想构建一个范围十亿个元素;)
          • 我完全同意。这也是一个常见的面试问题,他们需要确定性的解决方案。
          【解决方案6】:

          假设初始范围是 [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) 中完成。

          【讨论】:

            【解决方案7】:

            如果你有枚举器或集合操作,你可以用一种通用的方式来做。例如使用 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);
            }
            

            我要感谢一些现已删除的 cmets:

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

            • 分配是公平的,因为每个元素都有相同的机会出现。

            • 以这种方式回答的优势是你表现出对现代“函数式”编程的理解,这可能会让面试官感兴趣。

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

            【讨论】:

            • 既然这样的问题合适不合适的问题一直是brought up on meta,那就去那里讨论吧。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2020-10-03
            • 2019-08-31
            • 2014-04-27
            • 2013-10-27
            • 2014-02-25
            相关资源
            最近更新 更多