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

打乱数组

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. STM32 HAL库学习 (3) 中断!

        中断在单片机开发中有着重中之重的地位.    中断即打断,实至CPU再执行当前程序时,由于系统出现了某种需要处理的紧急情况,CPU暂停正在执行的程序,转而去执行另一段特殊程序来处理的出现的紧急 ...

  2. 使用 Solon Cloud 的 Jaeger 做请求链路跟踪

    <dependency> <groupId>org.noear</groupId> <artifactId>jaeger-solon-cloud-plu ...

  3. 全网最详细 二进制 k8s v1.25.x文档

    二进制安装k8s v1.25.0 IPv4/IPv6双栈 Kubernetes 开源不易,帮忙点个star,谢谢了 介绍 kubernetes(k8s)二进制高可用安装部署,支持IPv4+IPv6双栈 ...

  4. [大数据]Hadoop简述

    1 Hadoop:发展沿革 摘要:1个人(Doug Cutting).2个公司(Google.Cloudera) 1.1 渊源 Hadoop项目 最初开发者/创始者: Doug Cutting(道格· ...

  5. Redis读书笔记(二)

    Redis对象系统 Redis对象 字符串(String)的底层实现方式 直接保存整数值:字符串对象保存的是整数值,且可以用long类型来表示. embstr编码的SDS:字符串对象保存的是一个长度小 ...

  6. 靶机渗透【billu_b0x】

    ip扫描 访问80端口 目录扫描 逐个访问 上传一个图片马,结果没有回显 显示file参数为空.请在"文件"参数中提供文件路径 打开发现有用户名 ![] 发现数据库连接的配置信息, ...

  7. 笔记:C++学习之旅---顺序容器

    笔记:C++学习之旅---顺序容器 STL = Standard Template Library   标准库模版 容器可以使用范围for输出或者迭代器进行输出 一个容器就是一些特定类型对象的集合.顺 ...

  8. C# 闭包类对弱引用的坑

    闭包.弱引用的简单概念,大佬们描述的很多,有不了解的可以看看: 理解C#中的闭包 - 黑洞视界 - 博客园 (cnblogs.com) C#弱引用(WeakReference) - 简书 (jians ...

  9. Vite-WeGPT聊天AI实例|vue3+pinia仿ChatGPT聊天界面

    基于vue3.x+vite4+pinia2仿chatgpt聊天模拟实例Vue3-WeGPT. 基于Vite4.x+Vue3+Pinia2+VEPlus+Vue3-Markdown等技术实现仿ChatG ...

  10. 咚咚咚,你的王国之泪已上线「GitHub 热点速览」

    本周最大的热点,莫过于 Mojo 语言了,几大媒体均有报道这门兼顾 Python 优点和性能的新语言.当然还有凭借 Switch 游戏<塞尔达传说·王国之泪>登上热榜,获得 3,500+ ...