给你一个整数数组 nums
,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能 的。
实现 Solution
class:
Solution(int[] nums)
使用整数数组nums
初始化对象int[] reset()
重设数组到它的初始状态并返回int[] shuffle()
返回数组随机打乱后的结果
示例 :
输入 ["Solution", "shuffle", "reset", "shuffle"] [[[1, 2, 3]], [], [], []] 输出 [null, [3, 1, 2], [1, 2, 3], [1, 3, 2]]
思路
有一个操作,对任意一个元素都可能发生,如果每个元素被操作的概率是相等的,那么这个操作才能公平的进行。
对于数组(长度为len)中每个数字,我们想方法将其中任意一个元素用1/len的概率(等可能)抽出来,然后len--,直到清空数组。我们将元素以拿出的先后顺序为依据形成一个排列,这就是一个公平的随机的排列。
Knuth算法就是一个以此过程对数组进行随机化操作。
算法过程
先介绍C++random(#include <random>)库实现随机数的两个对象。
std::random_device,真随机数类,直接从机器设备中选一个数出来,速度较慢。
std::mt19937,是一个基于32位的梅森旋转算法的对象,生成一个伪随机数,速度较快。
std::random_device rd;实现了一个rd对象。此后每次调用rd()就会获得一个随机数。
std::mt9937 mt(rd()),从rd()调一个随机数作为种子实现了一个mt对象。此后每次调用mt()就会获得一个随机数。
在实际执行Knuth算法中,我们不需要真实取出一个元素,而是只需要初始化i=len-1,从前i+1个数(0,1,2,...i)随机选一个与第i个位置交换即可。
//以------表示随机范围,以*表示取到
i 0 1 2 3 4
nums[i] 9 8 7 6 5
i
----*----
↓
nums[i] 9 8 5 6 7
i
----*--
↓
nums[i] 9 8 6 5 7
i
--*--
↓
nums[i] 9 6 8 5 7
i
--*
↓
nums[i] 9 6 8 5 7
i
*
↓
nums[i] 9 6 8 5 7
std::mt19937 mt;
void Knuth(vector<int>&vec){
const int len=vec.size();
for(int i=len-1;i>0;i--)
swap(vec[mt()%(i+1)], vec[i]);
//取余操作+1是为了能取到i,因为第i个位置也在备选行列,自己与自己交换也在公平随机之内
}
Code
class Solution {
private:
vector<int> val;
mt19937 mt;
const int len;
public:
Solution(vector<int>& nums) : val(nums), len(nums.size()){};
vector<int> reset() { return val; }
vector<int> shuffle() {
vector<int> ans(val);
Knuth(ans);
return ans;
}
void Knuth(vector<int>& vec) {
for (int i = len - 1; i > 0; i--)
swap(vec[mt() % (i+1) ], vec[i]);
}
};