题目概述:
题目链接:点我做题
题解
一、蓄水池算法
用一个数组restate
存储原来的状态,然后每次要返回一个随机排列时,首先拷贝原来的状态到数组tmp1
,然后创建一个用来返回的ret
数组,取每一个元素时,利用蓄水池算法,从tmp1
中等可能的取一个元素,放到对应位置,然后从tmp1
中删除该元素,循环往复即可。
至于怎么实现蓄水池算法,遍历一遍tmp1
数组,对每一个下标i,生成一个[0,i+1)
的随机数,如果这个随机数是0了,那么就让待插入ret的变量waitpush = tmp1[i]
,然后接着往下遍历。
根据概率知识可知,下标为i的元素在循环后称为插入ret的元素的条件是下标为i的元素的那一轮随机数等于0且下标
i
+
1...
s
i
z
e
−
1
i + 1...size - 1
i+1...size−1的随机数都不等于0,且他们是互斥的,概率等于:
1
i
i
i
+
1
.
.
.
s
z
−
1
s
z
=
1
s
z
\frac{1}{i}\frac{i}{i+1}...\frac{sz-1}{sz}=\frac{1}{sz}
i1i+1i...szsz−1=sz1
所以每个元素插入ret中的概率都是
1
s
z
\frac{1}{sz}
sz1,
s
z
sz
sz是当前tmp1数组的元素个数,所以取到这个元素后把这个元素移除就行,循环往复,就可以等概率的从所有排列中得到一组随机排列。
class Solution {
public:
Solution(vector<int>& nums)
{
restate = nums;
_size = nums.size();
}
vector<int> reset()
{
return restate;
}
vector<int> shuffle()
{
vector<int> ret(_size);
vector<int> tmp1(restate);
int cnt = 0;
while (cnt != _size)
{
int waitpush = 0;
for (int i = 0; i < tmp1.size(); i++)
{
if (rand() % (i + 1) == 0)
{
waitpush = tmp1[i];
}
}
ret[cnt++] = waitpush;
tmp1.erase(find(tmp1.begin(), tmp1.end(), waitpush));
}
return ret;
}
private:
vector<int> restate;
int _size;
};
时间复杂度:
O
(
n
2
)
O(n^2)
O(n2)
空间复杂度:
O
(
1
)
O(1)
O(1)
二、洗牌算法
对于待插入元素下标为i时,我们生成一个
[
i
,
s
z
−
1
]
[i,sz-1]
[i,sz−1]的随机数
j
j
j,然后把j下标对应元素和i下标对应元素交换即可。
这是什么道理呢,我们这样想,一个随机的排列,由排列组合的知识可知,第一个位置相当于从所有的sz个元素中随机取一个元素放到这里,第二个位置相当于从剩下下的sz-1个元素中找一个元素放到这个位置…
由数学归纳法可以证明,任何一个元素x出现在任何一个0~sz-1的下标的概率都为
1
s
z
\frac{1}{sz}
sz1
设
总
共
有
n
个
元
素
当
下
标
i
等
于
0
时
,
任
意
一
个
元
素
出
现
在
0
位
置
的
概
率
显
然
是
1
n
;
下
标
i
等
于
1
时
,
任
意
一
个
元
素
出
现
在
位
置
的
概
率
是
不
出
现
在
0
位
置
且
在
这
一
轮
被
抽
到
的
概
率
;
即
n
−
1
n
∗
1
n
−
1
=
1
n
当
下
标
i
等
于
k
时
,
任
意
一
个
元
素
出
现
在
k
位
置
的
概
率
是
之
前
轮
都
没
有
抽
到
这
一
轮
抽
到
了
的
概
率
的
乘
积
即
1
n
−
k
∗
∏
i
=
n
n
−
k
+
1
(
1
−
1
i
)
=
1
n
−
k
∗
n
−
k
n
=
1
n
设总共有n个元素\\ 当下标i等于0时,任意一个元素出现在0位置的概率显然是\frac{1}{n};\\ 下标i等于1时,任意一个元素出现在位置的概率是不出现在0位置且在这一轮被抽到的概率;\\ 即\frac{n-1}{n}*\frac{1}{n-1} = \frac{1}{n}\\ 当下标i等于k时,任意一个元素出现在k位置的概率是之前轮都没有抽到这一轮抽到了的概率的乘积\\ 即\frac{1}{n-k}*\prod_{i=n}^{n-k+1}(1-\frac{1}{i})\\ =\frac{1}{n-k}*\frac{n-k}{n}=\frac{1}{n}
设总共有n个元素当下标i等于0时,任意一个元素出现在0位置的概率显然是n1;下标i等于1时,任意一个元素出现在位置的概率是不出现在0位置且在这一轮被抽到的概率;即nn−1∗n−11=n1当下标i等于k时,任意一个元素出现在k位置的概率是之前轮都没有抽到这一轮抽到了的概率的乘积即n−k1∗i=n∏n−k+1(1−i1)=n−k1∗nn−k=n1
代码:
class Solution {
public:
Solution(vector<int>& nums):_origin(nums)
{
}
vector<int> reset()
{
return _origin;
}
vector<int> shuffle()
{
int sz = _origin.size();
vector<int> ret(_origin);
for (int i = 0; i < sz; i++)
{
//生成[i, n - 1]的一个随机数作为本次选中的下标
int j = rand() % (sz - i) + i;
int tmp = ret[j];
ret[j] = ret[i];
ret[i] = tmp;
}
return ret;
}
private:
vector<int> _origin;
};
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)