【每天学习一点算法 2026/01/05】打乱数组

每天学习一点算法 2026/01/05

题目:打乱数组

给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。打乱后,数组的所有排列应该是 等可能 的。

实现 Solution class:

Solution(int\[\] nums) 使用整数数组 nums 初始化对象

int\[\] reset() 重设数组到它的初始状态并返回

int\[\] shuffle() 返回数组随机打乱后的结果

这个题目其实就是洗牌算法,我们分析一下,最核心的问题就是如何保证打乱后数组的所有排列是 等可能 的。

如果我们要把数组中的元素一个一个的取出来放到打乱后的数组中,就需要保证每次取到每个元素的概率是相同的。

  1. 很容易就可以想到的一种方式就是,根据数组长度生成随机数取出元素,然后长度减一生成随机数取出元素,直到取出所有的元素。感觉上是每个元素每次被取出的概率是相等的,来验证一下:

    • 第 1 个:
      1 l e n \frac{1}{len} len1

    • 第 2 个:
      l e n − 1 l e n × 1 l e n − 1 = 1 l e n \frac{len - 1}{len} × \frac{1}{len - 1} = \frac{1}{len} lenlen−1×len−11=len1

    • 第 n 个:
      l e n − n − 1 l e n × 1 l e n − n − 1 = 1 l e n \frac{len - n - 1}{len} × \frac{1}{len - n - 1} = \frac{1}{len} lenlen−n−1×len−n−11=len1

    ok,概率是一样的。

    然后就是代码实现了

    typescript 复制代码
    class Solution {
      readonly source: number[] // 存放初始数据
      constructor(nums: number[]) {
        this.source = nums
      }
    	// 重设数组
      reset(): number[] {
        return this.source
      }
    	// 打乱数组
      shuffle(): number[] {
        const nums = [...this.source] // 克隆源数据
        const len = this.source.length
        // 循环打乱数组
        for (let i = 0; i < len; i++) {
          const random = Math.floor(Math.random() * (len - i)) // 生成 0 ~ len - i - 1 的随机整数作为随机下标
          const num = nums.splice(random, 1)[0] // 截取随机位置的元素
          nums.push(num) // 将随机元素放到数组末尾,随机下标范围会减少不用担心会选到重复元素
        }
        return nums
      }
    }
    
    /**
     * Your Solution object will be instantiated and called as such:
     * var obj = new Solution(nums)
     * var param_1 = obj.reset()
     * var param_2 = obj.shuffle()
     */
  2. 我们看到上面循环打乱时,循环到最后一次时只有一个元素了,这个随机似乎没啥用了,有什么方法可以直接避免这种情况呢?

    我们只遍历下标 0 到下标 len - 2 的元素,每次生成 [i, len - 1] 范围的随机位置的元素跟 i 交换,这样依然可以保证每个数字打乱在每个位置上的概率一致,其实本质上跟上面方法的思想是一致的

    typescript 复制代码
    // 打乱数组
    const shuffle: () => number[] = () => {
      const nums = [...this.source] // 克隆源数据
      const len = this.source.length
      // 循环打乱数组
      for (let i = 0; i < len - 1; i++) {
        const random = i + Math.floor(Math.random() * (len - i)) // 生成 i ~ len - i - 1 的随机下标
        ;[nums[i], nums[random]] = [nums[random], nums[i]] // 交换位置
      }
      return nums
    }

题目来源:力扣(LeetCode)

相关推荐
-dzk-19 分钟前
【滑动窗口】LC 3.无重复字符的最长子串
算法·滑动窗口
zzxdear21 分钟前
用 OR-Tools CP-SAT 求解最简单的柔性作业车间调度(FJSP)
算法
luj_176832 分钟前
变频技术核心原理揭秘
服务器·c语言·开发语言·经验分享·算法
rannn_11132 分钟前
【力扣hot100】图论专题+模板|DFS、BFS、拓扑排序...
java·算法·leetcode·深度优先·图论
在线考试系统推荐33 分钟前
2026年7月最新实测:三大考试软件导入试题能力对比
人工智能·学习·系统架构
_Narcissus_37 分钟前
B树概念及操作笔记(含完整代码实现)
c语言·数据结构·数据库·c++·笔记·b树·算法
l12586538 分钟前
# RAG多轮对话检索设计:Query重写如何让“那它呢“变成完整问题
前端·数据库·人工智能·python·算法·fastapi·milvus
小弥儿40 分钟前
GitHub今日热榜 | 2026-08-20:Agent 上下文数据库接棒
数据库·学习·开源·github
sunoo-2291 小时前
【C 语言标准 IO 入门】第二天学习笔记:文件操作核心函数 + 实战案例 + 踩坑合集
linux·笔记·vscode·学习
MartinYeung51 小时前
[论文学习]MPMA:针对模型上下文协议的偏好操纵攻击
人工智能·学习·安全