繁体   English   中英

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

[英]For each element in a list, choose that element and 3 random nonrepeating elements

我有一个大小为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应用程序的主线程中执行此操作,因此我可能应该做一些更有效的事情。

通过删除冗余过滤来改进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);
}

如果您不想生成具有所有值可供选择的数组(如果范围更大,可能会出现问题),显而易见的算法是:

将固定编号添加到选择中。 从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]  

这是可能的解决方案之一:

List<Integer> listOfIntegers = new ArrayList<>();

// add some entries to the list
for(int i = 0; i < 202; i++) {
listOfIntegers.add(i);
}

for(int element : listOfIntegers){
// create a new list that excludes the current element
List<Integer> exclusionList = listOfIntegers.stream().filter(x -> x != element).collect(Collectors.toList());
// shuffle the new list (if you don't do it the output will use the same elements after the first few iterations)
Collections.shuffle(exclusionList);
// print the results and use only the first 3 elements of the shuffled list
System.out.println(element + ", " + exclusionList.subList(0, 3));
}

这应该给出以下输出:

0, [38, 193, 51]
1, [60, 179, 30]
2, [46, 21, 13]
3, [43, 201, 74]
4, [28, 14, 97]
5, [38, 24, 22]
6, [177, 106, 53]

... 等等。

还有另一种方法比下面的原始答案更容易。 它需要制作一个单独的列表,从中选择内容,但是您不必对其进行随机组合。

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

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

这比每次都对数组进行混洗并将其删除并将其添加到大型列表的解决方案要快得多。 相反,您正在处理非常小的范围列表。 该算法稍微复杂一些,但工作量却少得多。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM