|
| 1 | +### 题目描述 |
| 2 | + |
| 3 | +这是 LeetCode 上的 **[384. 打乱数组](https://leetcode-cn.com/problems/shuffle-an-array/solution/gong-shui-san-xie-xi-pai-suan-fa-yun-yon-0qmy/)** ,难度为 **中等**。 |
| 4 | + |
| 5 | +Tag : 「洗牌算法」 |
| 6 | + |
| 7 | + |
| 8 | + |
| 9 | + |
| 10 | +给你一个整数数组 `nums`,设计算法来打乱一个没有重复元素的数组。 |
| 11 | + |
| 12 | +实现 `Solution` class: |
| 13 | +* `Solution(int[] nums)` 使用整数数组 `nums` 初始化对象 |
| 14 | +* `int[] reset()` 重设数组到它的初始状态并返回 |
| 15 | +* `int[] shuffle()` 返回数组随机打乱后的结果 |
| 16 | + |
| 17 | +示例: |
| 18 | +``` |
| 19 | +输入 |
| 20 | +["Solution", "shuffle", "reset", "shuffle"] |
| 21 | +[[[1, 2, 3]], [], [], []] |
| 22 | +输出 |
| 23 | +[null, [3, 1, 2], [1, 2, 3], [1, 3, 2]] |
| 24 | +
|
| 25 | +解释 |
| 26 | +Solution solution = new Solution([1, 2, 3]); |
| 27 | +solution.shuffle(); // 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。例如,返回 [3, 1, 2] |
| 28 | +solution.reset(); // 重设数组到它的初始状态 [1, 2, 3] 。返回 [1, 2, 3] |
| 29 | +solution.shuffle(); // 随机返回数组 [1, 2, 3] 打乱后的结果。例如,返回 [1, 3, 2] |
| 30 | +``` |
| 31 | + |
| 32 | +提示: |
| 33 | +* $1 <= nums.length <= 200$ |
| 34 | +* $-10^6 <= nums[i] <= 10^6$ |
| 35 | +* `nums` 中的所有元素都是 唯一的 |
| 36 | +* 最多可以调用 $5 * 10^4$ 次 `reset` 和 `shuffle` |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +### 洗牌算法 |
| 41 | + |
| 42 | +共有 $n$ 个不同的数,根据每个位置能够选择什么数,共有 $n!$ 种组合。 |
| 43 | + |
| 44 | +题目要求每次调用 `shuffle` 时等概率返回某个方案,或者说每个元素都够等概率出现在每个位置中。 |
| 45 | + |
| 46 | +我们可以使用 $Knuth$ 洗牌算法,在 $O(n)$ 复杂度内等概率返回某个方案。 |
| 47 | + |
| 48 | +具体的,我们从前往后尝试填充 $[0, n - 1]$ 该填入什么数时,通过随机当前下标与(剩余的)哪个下标进行值交换来实现。 |
| 49 | + |
| 50 | +对于下标 $x$ 而言,我们从 $[x, n - 1]$ 中随机出一个位置与 $x$ 进行值交换,当所有位置都进行这样的处理后,我们便得到了一个公平的洗牌方案。 |
| 51 | + |
| 52 | +> 对于下标为 $0$ 位置,从 $[0, n - 1]$ 随机一个位置进行交换,共有 $n$ 种选择;下标为 $1$ 的位置,从 $[1, n - 1]$ 随机一个位置进行交换,共有 $n - 1$ 种选择 ... 且每个位置的随机位置交换过程相互独立。 |
| 53 | +
|
| 54 | +代码: |
| 55 | +```Java |
| 56 | +class Solution { |
| 57 | + int[] nums; |
| 58 | + int n; |
| 59 | + Random random = new Random(); |
| 60 | + public Solution(int[] _nums) { |
| 61 | + nums = _nums; |
| 62 | + n = nums.length; |
| 63 | + } |
| 64 | + public int[] reset() { |
| 65 | + return nums; |
| 66 | + } |
| 67 | + public int[] shuffle() { |
| 68 | + int[] ans = nums.clone(); |
| 69 | + for (int i = 0; i < n; i++) { |
| 70 | + swap(ans, i, i + random.nextInt(n - i)); |
| 71 | + } |
| 72 | + return ans; |
| 73 | + } |
| 74 | + void swap(int[] arr, int i, int j) { |
| 75 | + int c = arr[i]; |
| 76 | + arr[i] = arr[j]; |
| 77 | + arr[j] = c; |
| 78 | + } |
| 79 | +} |
| 80 | +``` |
| 81 | +* 时间复杂度:$O(n)$ |
| 82 | +* 空间复杂度:$O(n)$ |
| 83 | + |
| 84 | +--- |
| 85 | + |
| 86 | +### 最后 |
| 87 | + |
| 88 | +这是我们「刷穿 LeetCode」系列文章的第 `No.384` 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。 |
| 89 | + |
| 90 | +在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。 |
| 91 | + |
| 92 | +为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:https://github.com/SharingSource/LogicStack-LeetCode 。 |
| 93 | + |
| 94 | +在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。 |
| 95 | + |
0 commit comments