对于列表中的每个元素,选择该元素和3个随机非重复元素

时间:2017-09-07 18:08:27

标签: java algorithm random permutation

我有一个大小为202的列表,其中整数为0 ... 201。我需要迭代这个列表,对于每个元素,我需要选择4个元素,其中一个是当前元素,剩下的3个是随机的非重复元素。

例如,

在第一次迭代中,我需要生成[0,r1,r2,r3],其中r1,r2,r3是1-201之间的不同随机数;

在第二次迭代中,我需要生成[1,r1,r2,r3],其中r1,r2,r3是0,2-201之间的不同随机数;

在第三次迭代中,我需要生成[2,r1,r2,r3],其中r1,r2,r3是0-1,3-201之间的不同随机数;

我知道一种方法可以做到这一点,但它需要太多的时间和空间,因为我将在Android应用程序的主线程中执行此操作,我应该做更高效的事情。

4 个答案:

答案 0 :(得分:2)

通过删除冗余过滤来改进Miroslav的方法:

List<Integer> integerList = new ArrayList<>();
for(int i = 0; i < 202; i++) {
    integerList.add(i);
}
List<Integer> exclusionList = new ArrayList<>();
exclusionList.addAll(integerList);
for(int element : integerList){
    exclusionList.remove(element);
    Collections.shuffle(exclusionList);
    System.out.println(element + ", " + exclusionList.subList(0, 3));
    exclusionList.add(element);
}

答案 1 :(得分:1)

如果你不想生成一个包含所有可供选择的值的数组(在范围较大的情况下可能会出现问题),那么显而易见的算法就是:

将固定数字添加到选择中。选择0到200之间的随机整数;如果它等于或大于选择中的数字,则加1;将其添加到选择中。然后选择0到199之间的随机整数;将它与选择中的每个数字进行比较;当你发现它等于或大于选择中的数字时,加1;如果它更小,或者你到了列表的末尾,将其插入那里。继续198,197,直到你选择了足够的数字。示例:

Range: 0-201  
Add fixed number: 0  
Selection: [0]  
Range: 0-200  
Select random number: 34  
34 > 0, 34 + 1 = 35
Selection: [0, 35]  
Range: 0-199  
Select random number: 102  
102 > 0, 102 + 1 = 103, 103 > 35, 103 + 1 = 104  
Selection: [0, 35, 104]  
Range: 0-198  
Select random number: 66  
66 > 0, 66 + 1 = 67, 67 > 35, 67 + 1 = 68, 68 < 102, insert before 102  
Selection: [0, 35, 68, 102]  

答案 2 :(得分:1)

以下是可能的解决方案之一:

malloc

这应该给出以下输出:

realloc

......等等。

答案 3 :(得分:1)

还有另一种方法可以做到这一点比下面的原始答案更容易。它需要创建一个单独的列表,您可以从中选择内容,但不必将其随机化。

这个想法是你从第二个列表中随机选择。然后,当您进行选择时,将所选项目与列表中的最后一项交换,并减少可用于下一次选择的项目数。这样,您无法再次重新选择相同的数字。如果您的一个选择是索引号,您只需交换该号码但不保留它。这很简单:

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

这是如何工作的?假设我= = 4,你选择的数字是7,9和4.然后:

7 + 4 = 11  11 % 10 = 1
9 + 4 = 13  13 % 10 = 3
4 + 4 = 8   8 % 10 = 8

这应该比每次调整数组的解决方案运行得快得多,并且删除并添加到大型列表中的内容。相反,您正在处理一个非常小的范围列表。算法稍微复杂一些,但它的工作量却少得多。

相关问题