想象一下,有一个元素列表如下:
1a, 2a, 3a, 4a, 5b, 6b, 7b, 8b
现在我们需要将它随机化,使得不超过2" a" s或2" b" s彼此相邻。例如,由于第二,第三和第四个元素,以下列表是不:
3a, 7b,8b,5b ,2a,1a,5b,4a
如何在不产生许多随机序列和许多三元组比较的情况下编写高效代码?
答案 0 :(得分:0)
创建两个箱子,一个用于a,一个用于b。从随机箱中挑选并记录箱。从随机箱中选择第二个号码。如果bin与之前的bin不同,则只记录bin。如果bin与之前相同,则强制下一个选择来自另一个bin。继续前进,只有当你从同一个垃圾箱中连续两个选择时才强制使用垃圾箱。
答案 1 :(得分:0)
我会假设:
a
和b
,以及基本思想是(概念上)首先构造一个有效的a
s和b
s序列,然后将实际元素随机分配给a
和{{1序列中的s。实际上,您可以并行执行这两个步骤;每次向序列添加b
时,都会从尚未分配的此类元素集中选择一个随机a
元素,并与a
元素类似。
(稍微)复杂的部分是在没有偏见的情况下构建有效序列,而这正是我将要关注的内容。
通常情况下,关键是能够以导致枚举的方式计算可能序列的数量。我们实际上并没有列举这些可能性 - 即使是中等长度的序列也需要很长时间 - 但我们确实需要知道每个前缀如何枚举以该前缀开头的序列。
我们不是按元素生成序列元素,而是以相同类型的一个或两个元素的块生成它。由于我们不允许两个以上相同类型的连续元素,因此最终序列必须是一系列交替的块。实际上,除了最开始的每个点,选择是选择一个还是两个"其他"类。一开始,我们必须选择其中的一种或两种,所以我们必须首先选择起始种类,之后所有类型都是固定的;我们只需要一个1和2的序列 - 代表一个元素或两个相同类型的元素 - 每一步都有交替的类型。 1和2的序列受到以下事实的约束:我们知道每种元素有多少元素,这对应于{1,2} - 序列的偶数和奇数位数的总和。
现在,让我们将b
定义为偶数和奇数和为f(m,n)
和m
的序列计数。 (使用CS而不是数学规则,我们假设第一个位置是0(偶数),但它实际上完全没有区别。)假设我们有6 n
s和4 a
秒。然后有b
个序列以f(6,4)
和a
序列开头,以f(4,6)
开头,因此有效序列的总数为b
现在,假设我们需要计算f(6,4)+f(4,6)
。假设f(m,n)
足够大,我们有两个选项:选择偶数类型的m
元素之一,或者选择偶数类型中的两个m
元素。在那之后,我们将交换偶数和奇数,因为下一个选择适用于另一种。
这直接导致了递归
m
我们可能会认为它是一种二维的斐波纳契递归。 (回想一下f(m, n) = f(n, m-1) + f(n, m-2)
;这里的区别是第二个参数,以及参数顺序在每次递归时都会触发的事实。
与Fibonacci数一样,在没有记忆的情况下天真地计算值会导致递归调用的指数性爆炸,更有效的策略是从fib(m) = fib(m-1) + fib(m-2)
开始计算整个表(其值为1,显然);本质上,一种动态的编程方法。我们也可以用memoization进行递归计算,效率稍差但可能更容易阅读。
现在,让我们假设我们已经安排f(0,0)
的计算速度适当快,或者因为我们预先建立了整个可能性范围。我们需要的f(m,n)
和m
的最大值,或者因为我们正在使用记忆递归解决方案,因此我们只需对任何给定的n
执行一次慢速计算。现在让我们构建随机序列。
假设有m,n
na
- 元素和a
nb
- 元素。由于我们不知道随机序列是以b
还是a
开头,因此我们需要先做出决定。我们知道有b
个有效序列从f(na,nb)
开始a
和f(nb,na)
个有效序列,因此我们首先生成一个小于{{{}的随机非负整数1}}。如果随机数小于b
,那么我们将以f(na,nb) + f(nb,na)
- 元素开头;否则我们将以f(na,nb)
元素开头。
做出这个决定之后,我们将按照以下步骤进行。我们知道下一个元素是什么类型以及每种元素的剩余数量,因此我们只需要知道是否选择一个或两个正确类型的元素。为了做出这个选择,我们生成一个小于a
的非负随机整数;如果它小于b
那么我们选择一个元素;否则我们选择两个元素。然后我们交换元素集,修复计数,并继续,直到f(m, n)
和f(n, m-1)
都为0。