在刷LeetCode的时候遇到这样一道题:
打乱一个没有重复元素的数组。
这道题其实不算难,略微一想其实能写出很多解法。有一种洗牌算法比较简单的实现了O(n)的时间和空间复杂度。我自己最开始实现如下:
let shuffle = function() {
let nums = [...this.arr]
let p = 0
let range = nums.length - 1
while (range > 0 ) {
let cur = Math.round(Math.random() * (range) + p )
let temp = nums[p]
nums[p] = nums[cur]
nums[cur] = temp
p++
range--
}
return nums
};
思路很简单: 使用洗牌算法 先在0-n-1中,取随机取一个数,和首位0交换 然后在1-n-1中随机取一个数,和当前首位1交换 ...
思路就是每次确定一个数,但是一定要机会平均。也就是说,在剩下的m个数取一个的时候,每一个的可能性要相等。 此时坑出现了。 最开始我使用这个方法,但很快我就发现不是绝对平均的。 Math.round(Math.random() * range + p)
想象一下在一个有刻度的直尺(0-10)上随机选一个点,这个点完全随机。然后四舍五入。取得数为1的概率,为1/10。即落在0.5到1.5之间都会是1。但是取得0或者10的概率,却只有1/20。即只有0-0.5/9.5-10。
0-10之间只有10个宽度为1的区间,却有11个数。
所以我们把这个尺子前后再增加0.5厘米。保证每一个数被挑选出来的概率觉得相等,在离散的一个个数组下标来说,这才是真的随机。
可能会产生疑惑的是,那-0.5 和 10.5这个刻度是不是会导致0和10的概率增加。答案是不会。我们研究的是区间概率,对于区间来说,无宽度的点是无穷小的。
let shuffle = function() {
let nums = [...this.arr]
let p = 0
let range = nums.length - 1
while (range > 0 ) {
let cur = Math.round(Math.random() * (range + 1) + p - 0.5)
let temp = nums[p]
nums[p] = nums[cur]
nums[cur] = temp
p++
range--
}
return nums
};