题目信息
题目地址:https://leetcode-cn.com/problems/shuffle-an-array/
给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。
实现 Solution class:
Solution(int[] nums) 使用整数数组 nums 初始化对象
int[] reset() 重设数组到它的初始状态并返回
int[] shuffle() 返回数组随机打乱后的结果
示例:
1 2 3 4 5
| 输入 ["Solution", "shuffle", "reset", "shuffle"] [[[1, 2, 3]], [], [], []] 输出 [null, [3, 1, 2], [1, 2, 3], [1, 3, 2]]
|
解释
1 2 3 4
| Solution solution = new Solution(); solution.shuffle(); // 打乱数组 并返回结果。任何 的排列返回的概率应该相同。例如,返回 solution.reset(); // 重设数组到它的初始状态 。返回 solution.shuffle(); // 随机返回数组 打乱后的结果。例如,返回
|
提示:
1 <= nums.length <= 200
-106 <= nums[i] <= 106
nums 中的所有元素都是 唯一的
最多可以调用 5 * 104 次 reset 和 shuffle
解法一:暴力
题目意思是让我们实现一个类,它测试就会创建对象并且调用打乱方法与重置方法。既然有重置的话打乱的修改不是在原数组上进行。第一是新数组第二是随机位置。

那我们就可以使用ArrayList与Random来实现
代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| class Solution { private int[] nums; private int[] result; private Random rand = new Random(); public Solution(int[] nums) { this.nums = nums; result = new int[nums.length]; } public int[] reset() { return nums; } public int[] shuffle() { List<Integer> arrayList = getArrayList(); for (int i = 0; i < nums.length; i++) { int index = rand.nextInt(arrayList.size()); result[i] = arrayList.get(index); arrayList.remove(index); } return result; } private List<Integer> getArrayList() { List<Integer> arrayList = new ArrayList<Integer>(); for (int i = 0; i < nums.length; i++) { arrayList.add(nums[i]); } return arrayList; } }
|
时间复杂度:最差有(51024)(n/2)*n,总体来说O(n^2)的时间复杂度
空间复杂度:由于要用额外数组无法避免的O(n)

解法二:优化
上面的解法效率确实是不够的,其实之前做了那么一系列数组的算法题,虽然都是初级合集的但明显我们能明白一个关于数组原地变换的一个点,就是通过交换减少规模
但这一题并不能让我们通过交换来减少一半的规模,因为随机取再与后面的交换虽然能达到一半的复杂度并全员随机打乱,但并不是完全随机。所以我们还是要遍历完整每个进行random,这样做还是有个好处就是不需要arrayList了这样可以将n^2的复杂度降为n
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| class Solution { private int[] nums; private int[] result; Random random = new Random();
public Solution(int[] nums) { this.nums = nums; result = nums.clone(); } public int[] reset() { result = nums; nums = nums.clone(); return result; } public int[] shuffle() { for (int i = 0; i < result.length; i++) { int index = random.nextInt(result.length - i) + i; int temp = result[index]; result[index] = result[i]; result[i] = temp; } return result; } }
|
空间复杂度:O(n)
时间复杂度:O(n)

总结
这一题主要需要考虑打乱是一个什么状态,操作逻辑有没有影响到“随机”,关于解法一与二采用了两种方式记录原数组与打乱的过程数组,由于解法一的打乱赋值过程分了两个容器list和result所以才可以简略的这样写一个空数组,每次都是完全的按照阶乘的一个选取情况。解法二为了减少生成list所带来n倍的复杂度,采用交换,这样就需要在打乱数组本身原地进行,如果是在原数组取一对赋值到打乱数组那么就会出现重复。还有一个点是重置方法的,我在解法一直接是返回原数组只能说在当前逻辑上是满足,但最好还是像解法二一样真正的对打乱数组进行还原而不是把原数组返回出去。并且这里有个需要注意的点,我们在数组赋值实际上指向同一个对象,整个时候我们重置了result数组但result数组改变会影响原数组nums也就是我们要把nums指向另一个,也就是nums = nums.clone();