LeetCode 384. Shuffle an Array (Shuffle 数组最经典题!!!)

  1. Shuffle an Array
    Medium
    Shuffle a set of numbers without duplicates.

Example:

// Init an array with set 1, 2, and 3.
int[] nums = {1,2,3};
Solution solution = new Solution(nums);

// Shuffle the array [1,2,3] and return its result. Any permutation of [1,2,3] must equally likely to be returned.
solution.shuffle();

// Resets the array back to its original configuration [1,2,3].
solution.reset();

// Returns the random shuffling of array [1,2,3].
solution.shuffle();

解法1:从数组尾部开始,每次从nums[0]到nums[i]中随机取一个数与nums[i]交换。如果碰巧取到nums[i]也没关系,只是多做一次swap而已。
这种方法不知道是叫Knuth shuffle还是Fisher-Yates shuffle?

代码如下:

   class Solution {
public:
    Solution(vector<int> nums): v(nums) {}
    
    /** Resets the array to its original configuration and return it. */
    vector<int> reset() {
        return v;
    }
    
    /** Returns a random shuffling of the array. */
    vector<int> shuffle() {
        vector<int> res = v;
        int n = v.size();
        for (int i = n - 1; i >= 0; --i) {
            int t = rand() % (i + 1);
            swap(res[i], res[t]);
        }
        return res;
    }
    
private:
    vector<int> v;
};

解法2:
类似解法1。只是从数组前面开始做。

    vector<int> shuffle() {
        vector<int> res = v;
        for (int i = 0; i < res.size(); ++i) {
            int t = i + rand() % (res.size() - i);
            swap(res[i], res[t]);
        }
        return res;
    }
    

下面这2个链接讲的非常好:
1)https://yjk94.wordpress.com/2017/03/17/%E6%B4%97%E7%89%8C%E7%9A%84%E6%AD%A3%E7%A1%AE%E5%A7%BF%E5%8A%BF-knuth-shuffle%E7%AE%97%E6%B3%95/
2)
https://www.cnblogs.com/luxiaoxun/archive/2012/09/09/2677267.html

下面内容引用自2):
1)在不知道文件总行数的情况下,如何从文件中随机的抽取一行,并且每行被抽中的概率相等?
伪代码如下:

Element RandomPick(file): 
Int count = 0; 
while(count <= file.size) 
    If(random(0,count) == 0) 
        picked = file[count]; 
    ++ count; 
Return picked 

事实上这就是蓄水池算法的k=1的特殊情形。
2)蓄水池算法:
对其进行扩展,即如何从未知或者很大样本空间随机地取k个数?
类比下即可得到答案,即先把前k个数放入蓄水池,对于第 i>=k+1,我们以 k/i 概率决定是否要把它换入蓄水池,换入时随机的选取一个作为替换项,这样一直做下去,对于任意的样本空间n,对每个数的选取概率都为k/n。也就是说对每个数选取概率相等。
伪代码如下:

init a reservoir with the size k
add the first k elements into the reservoir
for i = k+1 to N
    m = random(1,i);
    if(m < k)
        swap the m_th value and i_th value
end for

以蓄水池算法为基础的一道题目 (参考九章):
问题描述:给你一个 Google 搜索日志记录,存有上亿挑搜索记录(Query)。这些搜索记录包含不同的语言。随机挑选出其中的 100 万条中文搜索记录。假设判断一条 Query 是不是中文的工具已经写好了。

解法如下: 假设你一共要挑选 N 个 Queries,设置一个 N 的 Buffer,用于存放你选中的 Queries。对于每一条飞驰而过的Query,按照如下步骤执行你的算法:

  1. 如果非中文,直接跳过
  2. 如果 Buffer 不满,将这条 Query 直接加入 Buffer 中
  3. 如果 Buffer 满了,假设当前一共出了过 M 条中文 Queries,用一个随机函数,以 N / M 的概率来决定这条 Query 是否能被选中留下。
    3.1 如果没有选中,则跳过该 Query,继续处理下一条 Query
    3.2 如果选中了,则用一个随机函数,以 1 / N 的概率从 Buffer 中随机挑选一个 Query 来丢掉,让当前的 Query 放进去。

为什么这样做就可以实现等概率地抽取呢? 我们用 5 条 Queries 里挑 3 条来作为例子证明每条 Query 被挑中的概率都是 3/5。

  1. 依次处理每条 Query,前 3 条 Queries 直接进入 Buffer => [1,2,3],此时前 3 条 Queries 被选中的概率 100%
  2. 第 4 条 Query 处理时,有 3/4 的概率被留下,那么第 4 条 Query 被选中的概率此时就是 3/4。
  3. 第 4 条 Query 处理时,如果留下之后,会从 Buffer 中以 1/3 的概率踢走一条 Query。那么这些在 Buffer 中留下包含两种情况:一种是第 4 条 Query 没有被选中,概率为1/4;第二种是第 4 条 Query被选中的条件下,没有被踢走,概率是3/4 * 2/3。所以总概率是1/4 + 2/3 * 3/4 = 3/4。其中 1/4 是第 4 条 Query 没有被选中的概率。
  4. 综上在处理到第 4 条 Query 的时候,所有 Query 被选中的概率均为 3/4。
  5. 第 5 条 Query 处理时,有 3/5 的概率被留下,那么第 5 条 Query 被选中的概率此时就是 3/5。
  6. 第 5 条 Query 处理时,如果留下之后,会从 Buffer 中以 1/3 的概率踢走一条 Query。前4条Query能够顺利进入Buffer并被留下,首先要满足大前提,第四条Query处理后被留下来,概率为3/4。在这个大前提下,同3.分两种情况,第一种是第 5 条 Query没有被选中,概率是2/5;第二种是第 5 条 Query被选中的前提下,没有被踢走,概率是3/5 * 2/3。总概率为::3/4 * (2/5 + 2/3 * 3/5) = 3/5。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值