在这里插入图片描述
在这里插入图片描述

摘要

在日常开发中,我们经常会遇到需要“打乱顺序”的场景。比如:

  • 推荐系统需要随机排列候选内容,避免总是相同顺序。
  • 游戏发牌要做到公平,不能偏向某种结果。
  • 随机抽题,保证每次的试卷题目顺序不一样。

这道题正是要求我们在给定一个数组的情况下,实现一个算法来 随机打乱数组,并且要保证所有排列出现的概率相同。本文会带你从题目描述开始,逐步拆解题解思路,给出 Swift 可运行的代码,并结合实际场景来理解这个问题。

描述

题目要求我们设计一个类 Solution,支持以下操作:

  1. 初始化:用给定的数组初始化。
  2. reset():恢复到原始数组。
  3. shuffle():返回数组的随机打乱结果,并且所有结果的概率相同。

举个例子:

let solution = Solution([1, 2, 3])
print(solution.shuffle()) // 可能是 [3,1,2] 或 [2,3,1] 等
print(solution.reset())   // 必须返回 [1,2,3]
print(solution.shuffle()) // 又可能是 [1,3,2] 或其他

这就像是发扑克牌一样,你必须保证每次洗牌后的结果是公平的。

题解答案

实现这个需求,核心就是要用 Fisher-Yates 洗牌算法(又叫 Knuth Shuffle)。

它的思路很简单:

  • 从数组的第一个元素开始,每次从后面的元素里随机挑一个,交换位置。
  • 这样可以确保每种排列出现的概率相同。

这个方法比“直接随机重排”更高效,因为我们只需要 O(n) 的时间。

题解代码分析

下面是完整的 Swift 实现:

import Foundation

class Solution {
    private var original: [Int]
    private var array: [Int]

    init(_ nums: [Int]) {
        self.original = nums
        self.array = nums
    }

    func reset() -> [Int] {
        array = original
        return array
    }

    func shuffle() -> [Int] {
        var shuffled = array
        for i in 0..<shuffled.count {
            let j = Int.random(in: i..<shuffled.count)
            shuffled.swapAt(i, j)
        }
        return shuffled
    }
}

代码解析

  1. original 用来保存最初的数组,方便 reset() 时恢复。

  2. array 是一个副本,用来进行打乱操作。

  3. reset() 很简单,就是把 array 重置成 original

  4. shuffle() 里用的是 Fisher-Yates 洗牌

    • 遍历数组索引 i。
    • 随机选择一个 j ∈ [i, count-1]
    • 交换 shuffled[i]shuffled[j]

这样保证了每次打乱都是公平的。

示例测试及结果

我们写一个 Demo 来看看效果:

let solution = Solution([1, 2, 3])

print("第一次 shuffle:", solution.shuffle())
print("reset 后:", solution.reset())
print("第二次 shuffle:", solution.shuffle())
print("第三次 shuffle:", solution.shuffle())

可能的输出结果是:

第一次 shuffle: [2, 1, 3]
reset 后: [1, 2, 3]
第二次 shuffle: [3, 1, 2]
第三次 shuffle: [1, 3, 2]

每次 shuffle() 的结果都可能不同,这正是题目要求的“等概率随机”。

时间复杂度

  • reset():O(1),直接返回原数组。
  • shuffle():O(n),因为我们要遍历数组并交换。

整体非常高效,即使调用上万次也能轻松运行。

空间复杂度

  • 需要存储一份原始数组 original,所以是 O(n)
  • 其他只用了常量额外空间。

总结

这道题本质上是考察 随机算法 的正确性与公平性。
很多人一开始可能会想到“用系统自带的 shuffle 方法”,但关键在于理解 为什么 Fisher-Yates 洗牌能保证所有排列等概率,这在实际应用中尤其重要。

比如:

  • 在考试系统中,题目打乱要公平,不能出现某些排列概率偏大。
  • 在游戏发牌中,要避免作弊嫌疑,必须做到完全等概率。
  • 在推荐系统中,随机打乱可以避免“推荐疲劳”。

所以,这道题虽然看起来是个小玩意儿,但背后的思想在工程中非常实用。

Logo

加入「COC·上海城市开发者社区」,成就更好的自己!

更多推荐