【LeetCode.384打乱数组】Knuth洗牌算法详解

这篇具有很好参考价值的文章主要介绍了【LeetCode.384打乱数组】Knuth洗牌算法详解。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前两天看网易面筋得知网易云的随机歌曲播放使用了这个算法,遂找题来做做学习一下

打乱数组

https://leetcode.cn/problems/shuffle-an-array/

给你一个整数数组 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]

提示:

1 <= nums.length <= 50
-106 <= nums[i] <= 106
nums 中的所有元素都是 唯一的
最多可以调用 104 次 reset 和 shuffle

题目要求的输入是一个整数数组nums,要调用shuffle()函数将其打乱后返回一个乱序数组

暴力法

(本方法不是重点,想了解直接看后面的代码,LeetCode官解)

一种很古朴的思路是:

再定义一个数组nums4shuffle,把nums中的所有数都存进nums4shuffle,然后遍历nums4shuffle

假设当前循环变量是i,此时从nums4shuffle中随机选取一个数,作为打乱后的nums的第i个元素

那么要解决的问题有以下几个:

1、如何再次获取数组的初始顺序

2、如何从nums4shuffle中随机取值

Fisher-Yates 洗牌算法

在上述问题中,所谓的“打乱”(或者说随机洗牌),其实可以理解为:让每一个元素都等概率地出现在每一个位置

即每个位置都能等概率的放置每个元素

听上去有点耳熟?Knuth 洗牌算法实际上就是一种将数组中元素随机排列的组合问题。

假设有一个长度为 n 的数组 arr,我们需要对其进行随机化操作,使得其中的每个元素都具有相等的可能性出现在任意位置上。这可以理解为是从 n 个元素中选择 n 个元素不重复地排列的问题,即全排列。因此,根据组合数学的知识,共有 n! 种不同的可能性,每一种可能性出现的概率应该是相等的,即为 1/n!

因此,Knuth 洗牌算法的正确性在于它能够保证每个排列出现的概率相等,并且所有可能的排列构成了一个大小为 n! 的集合。这与概率论中的组合问题有着相似的思路和方法。

算法实现

该算法使用代码实现起来很简洁,就是一个for循环即可

void knuth_shuffle(vector<int>& arr) {
    int n = arr.size();
    for (int i = 0; i < n; ++i) {
        // 随机选择一个位置 j,其中 i <= j < n
        int j = rand() % (n - i) + i;
        // 交换 arr[i] 和 arr[j] 的值
        swap(arr[i], arr[j]);
    }
}

knuth_shuffle 函数是用于执行 Knuth 洗牌算法的函数,它接受一个整数类型的数组 arr 作为输入参数,使用该算法对数组进行随机化操作。

函数中首先获取数组的长度 n,然后开始遍历数组。在每一轮遍历中,函数会随机选择一个位置 j,其中 i <= j < n,也就是从 i 开始到数组末尾之间随机选择一个位置。

这里使用了 rand() 函数来生成随机数,并将其除以取模运算的余数与 i 相加,得到最终的位置 j, rand() 函数默认生成的随机数范围是 0 到 RAND_MAX(通常为 32767)。

假设n=5,i=2,此时已经到了第二轮循环,前两个数已经被随机交换,现在要在剩下的3个数中进行交换

rand()函数会生成0到32767之间的一个随机整数,我们将它除以(n-i)=3,然后取余数

假设rand()生成的随机整数为10000,则它除以3的结果是3333余1。以此类推,我们就可能得到0~2之间的余数

当前遍历到的位置是2,那么只要在加上2就可以得到一个2~4之间的随机数

根据上面的分析,j的结果就是n - i之间的一个随机数

一旦选定了位置 j,函数就会交换 arr[i]arr[j] 这两个元素的值。

【LeetCode.384打乱数组】Knuth洗牌算法详解【LeetCode.384打乱数组】Knuth洗牌算法详解

循环结束

【LeetCode.384打乱数组】Knuth洗牌算法详解

这样,每一次遍历都会使得数组中的某个元素被随机地交换到前面的位置上,从而实现了 Knuth 洗牌算法的效果文章来源地址https://www.toymoban.com/news/detail-479368.html

代码

洗牌算法
class Solution {
public:
    Solution(vector<int>& nums) {        
        nums4save = nums;//初始化nums4save
    }
    
    vector<int> reset() {
        return nums4save;//重置数组时只要返回保存的初始数组即可
    }
    
    vector<int> shuffle() {
        vector<int> nums4shuffle = nums4save;//定义一个新数组用于打乱顺序
        int numsLen = nums4shuffle.size();
        //洗牌算法
        for(int i = 0; i < numsLen; ++i){//通过for循环选取一个数
        //在(i,numsLe]间再随机选择一个数与for循环选择的数进行交换
            int random = rand() % (numsLen - i) + i;//计算numsLen - i之间的一个随机数
            swap(nums4shuffle[i], nums4shuffle[random]);//交换
        }
        return nums4shuffle;//返回打乱后的数组
    }
private:
    vector<int> nums4save;//定义nums4save用于保存初始数组
};
暴力法
class Solution {
public:
    Solution(vector<int>& nums) { // 构造函数
        // 将传入的nums保存到成员变量this->nums中
        this->nums = nums;
        // 创建一个与nums等长的vector original,并将nums的值复制到original中
        this->original.resize(nums.size());
        copy(nums.begin(), nums.end(), original.begin());
    }
    
    vector<int> reset() { // 还原为原始顺序
        // 将original中的元素值复制到nums中
        copy(original.begin(), original.end(), nums.begin());
        // 返回nums
        return nums;
    }
    
    vector<int> shuffle() { // 随机打乱顺序
        // 创建一个新的vector shuffled,用于保存随机打乱后的nums
        vector<int> shuffled = vector<int>(nums.size());
        // 创建一个list lst,并将nums中的元素值复制到lst中
        list<int> lst(nums.begin(), nums.end());
      
        // 遍历nums
        for (int i = 0; i < nums.size(); ++i) {
            // 在lst的元素个数范围内生成一个随机索引j
            int j = rand()%(lst.size());
            // 获取lst中索引为j的元素,并将其赋值给shuffled[i]
            auto it = lst.begin();
            advance(it, j);//将迭代器 it 向前移动 j 个位置,就可以获得对应的随机元素
            shuffled[i] = *it;
            // 从lst中删除索引为j的元素
            lst.erase(it);
        }
        // 将shuffled中的元素值复制到nums中
        copy(shuffled.begin(), shuffled.end(), nums.begin());
        // 返回nums
        return nums;
    }
private:
    vector<int> nums; // 原始数组
    vector<int> original; // 原始顺序
};

到了这里,关于【LeetCode.384打乱数组】Knuth洗牌算法详解的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【算法】Fisher-Yates 洗牌算法

    Fisher-Yates 洗牌算法代码如下: 对于代码的解释 Math.random() * (i + 1) 是一个生成0到1之间的随机数乘以 (i + 1) 的表达式。 在 Fisher-Yates 洗牌算法中,这个表达式用于生成一个介于 0(包含)和 (i + 1) (不包含)之间的随机数。其中 i 是当前迭代的索引值,它表示从数组的最后一个

    2024年02月09日
    浏览(34)
  • 简单的洗牌算法

    目录 前言 问题 代码展现及分析  poker类 game类  Text类 洗牌算法为 ArrayList具体使用的典例,可以很好的让我们快速熟系ArrayList的用法。如果你对ArrayList还不太了解除,推荐先看本博主的 ArrayList的详解。 ArrayList的详解_WHabcwu的博客-CSDN博客 我们需要一副完整的扑克牌,除去大

    2024年02月12日
    浏览(34)
  • 算法练习--leetcode 数组

    输入n阶楼梯,每次爬1或者2个台阶,有多少种方法可以爬到楼顶? 示例1:输入2, 输出2 一次爬2阶; 一次爬1阶; 故两种方法。 示例2: 输入3, 输出3 三个1; 一个1 + 一个 2; 一个2 + 一个1; 思路分析: 采用递归求解 python实现: java实现 : 类似爬楼梯问题。   给定一个 整

    2024年02月14日
    浏览(41)
  • LeetCode 算法题 (数组)存在连续3个奇数的数组

    输入一个数组,并输入长度,判断数组中是否存在连续3个元素都是奇数的情况,如果存在返回存在连续3个元素都是奇数的情况,不存在返回不存在连续3个元素都是奇数的情况 例一: 例二:

    2024年02月21日
    浏览(38)
  • LeetCode面试算法-力扣 88. 合并两个有序数组

    88. 合并两个有序数组 题目描述     给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意: 最终,合并后数组不应由函数返回,而是存储

    2024年02月10日
    浏览(49)
  • LeetCode算法心得——k-avoiding 数组的最小总和(标记数组)

    大家好,我是晴天学长,这是一个细节题和一部分的思维题哈! 2) .算法思路 k-avoiding 数组的最小总和 1,填充一个1到n 的Boolean的数组 要n个数,但是数组大小不能确定。 所以建立1000的大小。 2.遍历筛选,如果数组中有这个的话,标记为false。 3.监测是否是false,true就sum++(前

    2024年02月11日
    浏览(46)
  • 力扣(LeetCode)算法_C++—— 两个数组的交集

    给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1: 输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2] 示例 2: 输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4] 输出:[9,4] 解释:[4,9] 也是可通过的

    2024年02月09日
    浏览(35)
  • Leetcode刷题详解——长度最小的子数组

    给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其总和大于等于 target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0 。 示例 1: 示例 2: 示例 3: 提示: 1 = target = 109 1 = nums.length

    2024年02月07日
    浏览(49)
  • 算法leetcode|53. 最大子数组和(rust重拳出击)

    给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组 是数组中的一个连续部分。 1 = nums.length = 10 5 -10 4 = nums[i] = 10 4 面对这道算法题目,二当家的再次陷入了沉思。 刚开始以为要暴力破解,双循环什么的,但

    2024年02月08日
    浏览(43)
  • 算法leetcode|88. 合并两个有序数组(rust重拳出击)

    给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2 ,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。 注意 :最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况

    2024年02月05日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包