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();
 */
posted @ 2022-07-22 11:13  ggaoda  阅读(2)  评论(0编辑  收藏  举报  来源