引言
洗牌算法,简单来说就是给定若干个元素,每次执行洗牌算法之后要基本等概率地给出这些元素的排列,即字面意思上的洗牌。
例题
【LEETCODE 384】
给一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能的。
Solution(int[] nums) | 使用整数数组 nums 初始化对象 |
---|---|
int[] reset() | 重设数组到它的初始状态并返回 |
int[] shuffle() | 返回数组随机打乱后的结果 |
算法描述
关键在于保证等概率,Knuth-Shuffle算法的做法是:从第1个位置开始依次到最后,随机从该位置元素到最后一个位置元素中随机等概率抽一个,把抽中的元素交换到该位置。
分析一下,假设有
n
n
n个元素,每个元素出现在第1个位置的概率是
1
n
\frac 1 n
n1,依次类推,每个元素出现在第
i
i
i个位置(
i
i
i从1开始)上的概率是:
P
i
=
n
−
1
n
×
n
−
2
n
−
1
×
⋯
×
1
n
−
i
+
1
=
1
n
P_{i}=\frac {n-1} {n} \times \frac {n-2} {n-1} \times \cdots \times \frac {1} {n-i+1} = \frac {1} {n}
Pi=nn−1×n−1n−2×⋯×n−i+11=n1
满足等概率性,且由于每次抽取元素后采取的行为是交换,所以不会重复。综上,满足洗牌算法的要求。
分析一下复杂度,不难看出,空间复杂度
O
(
n
)
O(n)
O(n),时间复杂度
O
(
n
)
O(n)
O(n)。
代码实现
class Solution {
public:
vector<int> a;
Solution(vector<int>& nums) {
a = nums;
}
vector<int> reset() {
return a;
}
vector<int> shuffle() {
auto tmp = a;
int n = a.size();
for (int i = 0; i < n - 1; i++) {
int rd = rand()%(n-i) + i;
swap(tmp[i], tmp[rd]);
}
return tmp;
}
};
关键词
洗牌算法 Knuth-Shuffle