下面还有另一种比我原来的答案更容易的方法。它需要制作一个单独的列表,您可以从中选择内容,但您不必随机排列。
这个想法是您从第二个列表中进行随机选择。当您进行选择时,您会将所选项目与列表中的最后一个项目交换,并减少可用于下一个选择的项目数。这样,您就不能再次重新选择相同的号码。如果您的选择之一是索引号,您只需交换该数字但不要保留它。真的很简单:
selectionList = array containing 0..maxNumber
for i in 0 to maxNumber
selectionListLength = selectionList.length
selections = []
numSelected = 0
while numSelected < 3
pick = random(selectionListLength)
// if we picked any number other than i, keep it.
if selectionList[pick] != i
// keep the number
selections[numSelected] = selectionList[pick]
numSelected++
end if
// swap selected item with the one at the end of the list
// and reduce the count available
temp = selectionList[pick]
selectionList[pick] = selectionList[selectionListLength-1-numSelected]
selectionList[selectionListLength-1-numSelected] = temp
--selectionListLength
end while
end for
随着时间的推移,这最终会扰乱选择列表,但这不应影响您选择的“随机性”。这真的很容易编码,不需要重新洗牌,而且比我原来的回复更容易理解。它最多会为每个数字从列表中随机选择 4 个(但通常是 3 个)。
原答案
您应该能够在 O(n) 时间内完成此操作,而无需生成临时列表或进行任何改组。
假设您有一个包含 10 个数字的列表:
0 1 2 3 4 5 6 7 8 9
所以你从 0 开始。现在你需要三个不重复的随机数,它们介于 1 和 9 之间,包括 1 和 9。所以选择一个大于或等于 1 且小于 9 的随机数。然后加 1。假设您选择了 5。您的列表现在分为四个部分:
0 | 1 2 3 4 | 5 | 6 7 8 9
将这两个范围 [1-4] 和 [6-9] 添加到列表中。选择其中一个并从中选择一个数字。假设您选择了第一个列表并选择了数字 2。现在您有了范围:
0 | 1 | 2 | 3 4 | 5 | 6 7 8 9
将两个新范围([1-1] 和 [3-4])添加到已包含 [6-9] 的范围列表中。现在再次从列表中选择其中一个范围并从中选择一个数字。
事情变得有点复杂,因为从一个范围中选择一个数字并不总是将它一分为二。例如,如果您从范围 [1-4] 中选择数字 4,那么您将只剩下单个范围 [1-3]。但我们可以很容易地解释这一点。
这是一些伪代码。
ranges = [(1-9)] // a single range from 1-9
selections = [] // array for selected numbers
for s in 0,1,2
x = random(ranges.length) // select one of the ranges
range = ranges[x]
ranges.remove(x) // remove the range from the list of ranges
// now, pick a number from that range
pick = random(range.last-range.first+1) // pick a random number from that range
selections[s] = range[0]+pick // and save the selection
// create new range or ranges that exclude that number
if (selections[s] != range.first)
ranges += (range.first, range.first+pick-1)
if (selections[s] != range.last)
ranges += (range.first+pick+1, range.last)
end for
// at this point, the selections array contains the numbers you selected.
请注意,这是伪代码。我上面显示的ranges 数组是一个包含元组的数组:范围的开始和结束。您需要编写代码来创建这些元组对象,或包含开始值和结束值的对象。
当您选择与第一个索引 0 一起使用的随机数时效果很好。但是当您选择与 5 一起使用的随机数时呢?没问题。我们只是使用偏移量和模运算来映射基于 0 的数字。这很简单,只需将上面的代码用这个循环包装起来:
for i in 0 to maxNumber
// insert code above
// now we map the numbers from the selections array
selections[0] = (selections[0] + i) % maxNumber
selections[1] = (selections[1] + i) % maxNumber
selections[2] = (selections[2] + i) % maxNumber
end for
它是如何工作的?假设 i == 4,您选择的数字是 7、9 和 4。那么:
7 + 4 = 11 11 % 10 = 1
9 + 4 = 13 13 % 10 = 3
4 + 4 = 8 8 % 10 = 8
这应该比让你每次都洗牌一个数组、删除和添加东西到一个大列表的解决方案快得多。相反,您正在使用一个非常小的范围列表。该算法稍微复杂一些,但它做的工作要少得多。