这个问题见了很多,一般表述是,1到n的整数中随机选k个这k个不能重复。变一下即可以是n个元素选k个,不能重复。
有不同复杂度的算法。第一个是笨办法:
(1)随机选一个数,然后判断这个数字是否已经被选过了(是否重复),不过不重复,那就就选他了,如果重复,就再随机选一个。
空间复杂度:O(1)
时间复杂度:O(k^2)
而且有一个问题,如果是100个选90个,那么最后冲突的概率很大,选一个数字要重复随机好多次才行。效率低。不要。
如果用hash来解决判重的问题:
空间:O(n)
时间:O(k)
(2)第二种是第一种的改进。先在1到n-k+1中随机选一个,然后把范围扩大到n-k+2,再随机选一个,如果随机选中的是之前已经选过的,那么就选择第n-k+2个元素。
比如:从1-8中选4个数字。
第一步.从1到5中随机选一个,比如选了2.
第二步。从1到6中随机选一个。如果选出的是i=1,3,4,5,6,那么就选i即可。如果选出的是2,这个时候就重复了,此时的处理方式是选择i=6。
第三步,从1到7中随机选一个数,如果跟之前的重复,那么就选那种7.
第四步从1到8中随机选一个数,如个之前的重复,就选8。
空间复杂度:O(1)
时间复杂度:O(k^2)
这种方法确保每次都能产生随机数。避免了第一种方法的问题。
方法1和方法2都可以用hash来解决判重时的消耗。
此时方法2的复杂度为:
空间:O(n)
时间:O(k)
(3)方法3是经典的交换法。
产生第一个随机元数,将其交换到数组的第一个位置a[0]。这样,只要在a[1]和a[n-1]中随机选泽,即可以确保不重复了。
所以,再在a[1]到a[n-1]中产生一个随机数,交换到a[1]位置,。。。。
空间:O(n)
时间:O(k)
(4)因为是从n个中产生k个,所以每个元素选中的概率是k/n。
所以,对于a[0],以k/n的概率选泽他。如果选中了,那么对剩下的数组来说,就是在n-1个元素中选k-1个元素的问题;如果没选中,那么
对剩下的数组来说,就是在n-1个元素中选k个元素的问题。迭代即可。
时间复杂度:O(n)
空间复杂度:O(1)
这个算法不一定要遍历到最后才结束,如果已经选够了n个数,就可以结束了。
(5)先选中前k个元素,然后开始遍历剩下的元素,如第k+1个元素来的时候,以k/(1+k)的概率去替换之前k个元素中的一个。至于替换哪一个,就是均匀的选了。
对于新来的这个元素,被选中的概率是:k/(k+1) (替换)
对于之前的元素,被选中的概率是:(1-k/(k+1))+k/(k+1) * (k-1)/k = 1/(k+1) + (k-1)/(k+1)= k/(+1) (不替换+替换但没被选中替换出去)
第k+2个元素来的时候,以k/(k+2)的概率去替换之前k个元素中的一个。
时间复杂度:O(n)
空间复杂度:O(1)
这种方法还适合在总数量未知的情况下使用。
O(n)的算法未必比O(k^2)的算法好,注意n和k的区别。比如在全体4字节长的整数中随机选3个不重复的,就不要用(4)和(5)了。