暴力法
每次选取一个元素放入结果数组,然后将该元素删除。
证明:某个元素第k轮被选取的概率是:前k-1轮未被选中 * 第k轮选中。
1
n
−
k
⋅
∏
i
=
1
k
n
−
i
n
−
i
+
1
\frac{1}{n-k} \cdot \prod_{i=1}^{k} \frac{n-i}{n-i+1}
n−k1⋅i=1∏kn−i+1n−i
展开之后;
( n − 1 n ⋅ n − 2 n − 1 ⋅ ( … ) ⋅ n − k + 1 n − k + 2 ⋅ n − k n − k + 1 ) ⋅ 1 n − k \left(\frac{n-1}{n} \cdot \frac{n-2}{n-1} \cdot(\ldots) \cdot \frac{n-k+1}{n-k+2} \cdot \frac{n-k}{n-k+1}\right) \cdot \frac{1}{n-k} (nn−1⋅n−1n−2⋅(…)⋅n−k+2n−k+1⋅n−k+1n−k)⋅n−k1
最后总是等于1/n。
class Solution {
public:
Solution(vector<int>& nums) : data(nums){}
/** Resets the array to its original configuration and return it. */
vector<int> reset() {
return data;
}
/** Returns a random shuffling of the array. */
vector<int> shuffle() {
vector<int> backup(data), res;
int n = backup.size();
for(int i = 0; i < n; ++i){
int idx = rand()%backup.size();
res.push_back(backup[idx]);
backup.erase(backup.begin()+idx);
}
return res;
}
private:
vector<int> data;
};
效率很一般。
洗牌算法
参考:洗牌算法深度详解 - 打乱数组 - 力扣(LeetCode)
每次迭代,生成一个范围在当前下标到数组末尾元素下标之间的随机整数。接下来,将当前元素和随机选出的下标所指的元素互相交换 - 这一步模拟了每次从 “帽子” 里面摸一个元素的过程,其中选取下标范围的依据在于每个被摸出的元素都不可能再被摸出来了(被放到前面去了)。
概率计算其实也适合上一个方法一样的逻辑。
class Solution {
public:
Solution(vector<int>& nums) : data(nums){}
/** Resets the array to its original configuration and return it. */
vector<int> reset() {
return data;
}
/** Returns a random shuffling of the array. */
vector<int> shuffle() {
vector<int> res(data);
int n = res.size();
for(int i = 0; i < n; ++i){
int idx = i + rand()%(n-i);//生成一个范围在当前下标到数组末尾元素下标之间的随机整数
swap(res[i], res[idx]);
}
return res;
}
private:
vector<int> data;
};