[384]. 打乱数组

Posted Debroon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[384]. 打乱数组相关的知识,希望对你有一定的参考价值。

[384]. 打乱数组

 


题目

输入
["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]

 


算法设计:洗牌算法(Knuth shuffle 算法)

洗牌算法(Knuth shuffle算法):对于有 n 个元素的数组来说,为了保证洗牌的公平性,应该要能够等概率的洗出 n! 种结果。

举例解释如下:

  • 开始数组中有五个元素;
  • 在前五个数中随机选一个数与第五个数进行交换,每个数都有五分之一的概率被交换到最后一个位置;
  • 在前四个数中随机选一个数与第四个数进行交换,每个数都有五分之一的概率被交换到第四个位置;
  • 在前三个数中随机选一个数与第三个数进行交换,每个数都有五分之一的概率被交换到第三个位置;
  • 综上所述,每个数都有相等的概率被放到任意一个位置中,即每个位置中出现任意一个数的概率都是相同的。这,就是洗牌算法。
class Solution 
private:
    vector<int> original;

public:
    Solution(vector<int>& nums) 
        original = nums;
    
    vector<int> reset() 
        return original;
      
    vector<int> shuffle() 
        vector<int> nums(original);                    // 用原数组来初始化新数组
        for (int i = nums.size() - 1; ~i; i -- )       // 从后往前遍历
            swap(nums[i], nums[random() % (i + 1)]);   // random() % (i + 1) 能随机生成 0 到 i 中的任意整数,+ 1 是为了自己也能被选,这样才算等概率
        return nums;
    
;

时间复杂度: θ ( n ) \\theta(n) θ(n)

空间复杂度: θ ( 1 ) \\theta(1) θ(1),洗牌算法原地排序

 


拒绝采样与洗牌算法的异同

拒绝采样也可以实现等概率分配,不过拒绝采样只能保证单次公平(概率相等),洗牌算法能保证多次公平。

比如,现在需要取 N 个不同的随机数。

  • N = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

拒绝采样的思路,如第一次随机抽的是 1,第二次、第三次也可能抽到 1,为了不同,会拒绝重复的随机数。

也可以实现,但是这样会产生重复抽取,当数组很大、采样的样本量也很大时,会产生无穷循环。

多次公平随机采样,我们会用洗牌算法。

洗牌算法思路:

  • 在数组 [1, 10] 中随机选择一个元素后,与倒数第一个元素交换位置
  • 在数组 [1, 9] 中随机选择一个元素后,与倒数第二个元素交换位置

每次把选中的随机元素放到后面,使得还没有选择的元素依然连续的排在前面。

由于排在前面的元素使用公平采样的方法,会等概率取到。

所以,这些元素在前面分布的顺序不重要,只需要他们连续的分布在前面。

所以,我们可以使用这种已经选择的元素和所有没有被选择的元素中最后一个元素,交换位置,保持所有没被选择的元素还在前面。

再对前面的元素实现公平采样,即可实现等概率多次采样。

一个细节就是:

random() % (i + 1)

random() % (i + 1) 能随机生成 0 到 i 中的任意整数。

+ 1 是为了自己也能被选,这样才算等概率。

比如,数组 [1,2,3,4,5]。

洗牌算法是思路是,随机选择一个元素与最后一个元素交换。

假设随机选择的是 5,最后一个元素也是 5,那么也需要交换,这样才是等概率的。

以上是关于[384]. 打乱数组的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 384. 打乱数组 / 859. 亲密字符串/ 423. 从英文中重建数字

p105 打乱数组(leetcode 384)

解题报告Leecode 384. 打乱数组——Leecode每日一题系列

LeetCode 384 打乱数组[洗牌] HERODING的LeetCode之路

LeetCode_Nov_4th_Week

2021-11-11:打乱数组。给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。实现 Solution class:Solutio(int[] nums) 使用整数数组 nums(