leetcode-384.打乱数组
数学问题-随机与取样
题目详情
给你一个整数数组 nums
,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能 的。
实现 Solution class
:
Solution(int[] nums)
使用整数数组 nums
初始化对象
int[] reset()
重设数组到它的初始状态并返回
int[] shuffle()
返回数组随机打乱后的结果
示例1:
输入
["Solution", "shuffle", "reset", "shuffle"]
[[[1, 2, 3]], [], [], []]
输出
[null, [3, 1, 2], [1, 2, 3], [1, 3, 2]]
解释
Solution solution = new Solution([1, 2, 3]);
solution.shuffle(); // 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。例如,返回 [3, 1, 2]
solution.reset(); // 重设数组到它的初始状态 [1, 2, 3] 。返回 [1, 2, 3]
solution.shuffle(); // 随机返回数组 [1, 2, 3] 打乱后的结果。例如,返回 [1, 3, 2]
思路:
可以利用 Fisher-Yates 洗牌算法,原理是通过随机交换位置来实现随机打乱,有正向
和反向两种写法,且实现非常方便。
洗牌算法的思想是(以反向洗牌为例):每次从未处理的数据中随机取出一个数字,然后把该数字放在数组的尾部,即数组尾部存放的是已经处理过的数字。具体实现如下:
我的代码:
class Solution
{
//vector<int> nums;
vector<int> origin;
public:
Solution(vector<int>& nums): origin(std::move(nums)) {}
/*这里等价于↓,目的是将nums拷贝至origin
Solution(vector<int>& nums)
{
//这种方法还需要借助一个类内的nums数组,因为传入的nums是引用形式
this->nums = nums;
this->origin.resize(nums.size());
copy(nums.begin(), nums.end(), origin.begin());
}
*/
vector<int> reset()
{
return origin;
}
vector<int> shuffle()
{
if (origin.empty()) return {};
vector<int> shuffled(origin);
int n = origin.size();
//反向洗牌
for (int i = n - 1; i >= 0; --i)
{
//每次交换末尾的数和 0~末尾之间的某个随机数
swap(shuffled[i], shuffled[rand() % (i + 1)]);
}
/*正向洗牌
for (int i = 0; i < n; ++i)
{
//pos是0 ~ n-i-1的一个随机数
int pos = rand() % (n - i);
//每次交换首部的数和 i~n-1之间的某个随机数
swap(shuffled[i], shuffled[i+pos]);
}
*/
return shuffled;
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(nums);
* vector<int> param_1 = obj->reset();
* vector<int> param_2 = obj->shuffle();
*/