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

打乱数组

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] 这两个元素的值。


循环结束

这样,每一次遍历都会使得数组中的某个元素被随机地交换到前面的位置上,从而实现了 Knuth 洗牌算法的效果

代码

洗牌算法
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洗牌算法详解的更多相关文章

  1. knuth洗牌算法

    首先来思考一个问题: 设计一个公平的洗牌算法 1. 看问题,洗牌,显然是一个随机算法了.随机算法还不简单?随机呗.把所有牌放到一个数组中,每次取两张牌交换位置,随机 k 次即可. 如果你的答案是这样, ...

  2. Java实现 LeetCode 384 打乱数组

    384. 打乱数组 打乱一个没有重复元素的数组. 示例: // 以数字集合 1, 2 和 3 初始化数组. int[] nums = {1,2,3}; Solution solution = new ...

  3. 随机洗牌算法Knuth Shuffle和错排公式

    Knuth随机洗牌算法:譬如现在有54张牌,如何洗牌才能保证随机性.可以这么考虑,从最末尾一张牌开始洗,对于每一张牌,编号在该牌前面的牌中任意一张选一张和当前牌进行交换,直至洗到第一张牌为止.参考代码 ...

  4. js 随机数 洗牌算法

    function shuffle(arr){ var len = arr.length; for(var i = 0;i<len -1;i++) { var idx = Math.floor(M ...

  5. 洗牌算法shuffle

    对这个问题的研究始于一次在群里看到朋友发的洗牌面试题.当时也不知道具体的解法如何,于是随口回了一句:每次从剩下的数字中随机一个.过后找相关资料了解了下,洗牌算法大致有3种,按发明时间先后顺序如下: 一 ...

  6. 519. Random Flip Matrix(Fisher-Yates洗牌算法)

    1. 问题 给定一个全零矩阵的行和列,实现flip函数随机把一个0变成1并返回索引,实现rest函数将所有数归零. 2. 思路 拒绝采样 (1)先计算矩阵的元素个数(行乘以列),记作n,那么[0, n ...

  7. 《Algorithms算法》笔记:元素排序(3)——洗牌算法

    <Algorithms算法>笔记:元素排序(3)——洗牌算法 Algorithms算法笔记元素排序3洗牌算法 洗牌算法 排序洗牌 Knuth洗牌 Knuth洗牌代码 洗牌算法 洗牌的思想很 ...

  8. 【算法】331- JS洗牌算法

    点击上方"前端自习课"关注,学习起来~ 最近的一个塔罗牌项目中,有一个洗牌的需求,其实也就是随机打乱数组,遂网上搜了下,再此做个整理- 塔罗牌 举例来说,我们有一个如下图所示的数组 ...

  9. 洗牌算法及 random 中 shuffle 方法和 sample 方法浅析

    对于算法书买了一本又一本却没一本读完超过 10%,Leetcode 刷题从来没坚持超过 3 天的我来说,算法能力真的是渣渣.但是,今天决定写一篇跟算法有关的文章.起因是读了吴师兄的文章<扫雷与算 ...

  10. 洗牌算法Fisher_Yates原理

    1.算法 http://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 简单的原理如下图所示: 2.原理 总结下,洗牌算法Fisher_Yates ...

随机推荐

  1. Midjourney AI绘画使用指南

    ​上图有Midjourney生成,提示语为:24-year-old Chinese woman with long hair and a Tedd Midjourney是一款基于Prompt设计和CL ...

  2. C++ 用户输入验证

    在编写程序时,请考虑用户将如何滥用您的程序,尤其是在文本输入方面.对于每个文本输入点,请考虑: 会不会提取失败? 用户可以输入比预期更多的输入吗? 用户可以输入无意义的输入吗 用户可以溢出输入吗? 以 ...

  3. 软件开发定律:海勒姆定律(Hyrum's Law)

    hi,我是熵减,见字如面. 在软件开发中,你是否遇到过这种情况: 你正在开发一个购物车的功能,需要在用户添加商品到购物车时,将商品的信息存储到数据库中.你设计了一个简单的方法,如下所示: public ...

  4. Python开发遇到的一些问题

    1.SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame.Try usin ...

  5. 【JSOI2008】最大值

    [JSOI2008]最大值 线段树裸题!动态RMQ. 这道题的操作是直接在序列末尾添加数值,所以连\(push_{down}\),以及建树什么的都不用了.. 这真是写过的最简短的一道\(seg_{tr ...

  6. java基础--lambda表达式

    lambda表达式,一种常见用法,就是简化匿名内部类.使用前提条件:如果一个方法A(),只涉及一个抽象方法待实现,那么使用A()时,涉及到匿名内部类,就可以简化为 lambda 表达式 lambda表 ...

  7. day65:Linux:nginx代理&nginx负载均衡

    目录 1.nginx代理 2.nginx代理与配置 3.nginx负载均衡调度多web节点(静态页面) 4.nginx负载均衡调度多应用节点(blog) 5.nginx_proxy + web应用节点 ...

  8. ARouter源码分析

    源码看过好几遍了,但是总是会忘记,特此记录下 先从注解处理器开始 BaseProcessor是其他三个注解处理器的抽象类,子类去实现process方法.在其中的init方法中会获取我们的module模 ...

  9. DG:有多个备库如何切换

    问题描述:有一数据库准备进行主备switchover切换,但是有两个备库,其中最早一个备库状态已经出现GAP,第二个备库状态正常 SQL> show parameter log_archive_ ...

  10. SMT贴片加工钢网工艺制作方法

    smt贴片加工过程中,首先要进行锡膏印刷,而锡膏印刷的工作原理就是用机器刮刀将锡膏推送到钢网的孔洞中,使锡膏与pcb板的电子元器件接触,为下一步焊接做准备.钢网的作用就是与pcb板焊盘位置固定,使锡膏 ...