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

打乱数组

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. 【译】使用 ChatGPT 和 Azure Cosmos DB 构建智能应用程序

    原文 | Mark Brown 翻译 | 郑子铭 随着对智能应用程序的需求不断增长,开发人员越来越多地转向人工智能(AI)和机器学习(ML),以增强其应用程序的功能.聊天机器人已经成为提供对话式人工智 ...

  2. 剑指offer 第 17 天

    第 17 天 排序(中等) 剑指 Offer 40. 最小的k个数 输入整数数组 arr ,找出其中最小的 k 个数.例如,输入4.5.1.6.2.7.3.8这8个数字,则最小的4个数字是1.2.3. ...

  3. GLM:通用语言模型

    ChatGPT已经火了一段时间了,国内也出现了一些平替,其中比较容易使用的是ChatGLM-6B:https://github.com/THUDM/ChatGLM-6B ,主要是能够让我们基于单卡自己 ...

  4. 投资组合计算分析——R语言

    "投资组合"是指金融资产(如股票.债券和现金)的任何组合.投资组合有很多类型,包括市场投资组合和零投资投资组合.可以使用以下任何一种投资方法和原则来管理投资组合的资产分配:股息加权 ...

  5. 在Kubernetes(k8s)中部署 jenkins

    在Kubernetes(k8s)中部署 jenkins YAML配置文件 由于jenkins需要持久化存储,通过nfs动态供给pvc存储卷. 可以参考我之前的文档:https://cloud.tenc ...

  6. must be reducible node 错误

    "must be reducible node"错误通常是由于使用了无法转换为表达式树的代码或表达式. 场景再现:在项目中使用GroupBy的时候,对字段进行了类型转换,接下来正常 ...

  7. CI框架调用第三方类库

    public function index() { //调用第三方类库 /* * 注意事项: * library 里面调用的名字首字母必须是 大写 * 使用它的方法时 使用小写 */ $this-&g ...

  8. 如何玩转国产神器:接口一体化协作平台Apifox!

    前言:Apifox是什么? 简介: 简单来说,Apifox = swagger + mock + postman+Jmeter,是API 文档.API 调试.API Mock.API 自动化测试一体化 ...

  9. win10环境下 VMware Workstation Pro 安装centos7无法上网

    一.安装centos7 网上类似的教程太多了,我就不一一写了,提供两个网址,先按照教程安装 VMware Workstation Pro ,秘钥在第二个链接里面(亲测可用), 安装完VMware在根据 ...

  10. 洛谷:P5716日份天数

    题目描述 输入年份和月份,输出这一年的这一月有多少天.需要考虑闰年. 输入格式 输入两个正整数,分别表示年份 \(y\) 和月数 \(m\),以空格隔开. 输出格式 输出一行一个正整数,表示这个月有多 ...