打乱一个没有重复元素的数组。
示例:
// 以数字集合 1, 2 和 3 初始化数组。
int[] nums = {1,2,3};
Solution solution = new Solution(nums);
// 打乱数组 [1,2,3] 并返回结果。任何 [1,2,3]的排列返回的概率应该相同。
solution.shuffle();
// 重设数组到它的初始状态[1,2,3]。
solution.reset();
// 随机返回数组[1,2,3]打乱后的结果。
solution.shuffle();
Fisher-Yates 洗牌算法
在每次迭代中,获取一个在当前坐标到数组末尾区间的随机整数。接下来,将当前元素和随机选出的下标所指的元素互相交换,元素是允许和它自己交换
nums[] int [1,2,3,4,5]
i=0 随机坐标的区间就是[0,5) 假设是随机到坐标为1 那交换后变成 [2,1,3,4,5]
i=1 随机坐标的区间就是[1,5) 假设是随机到坐标还是1 那就和自己交换后变成 [2,1,3,4,5](其实就是没变)
i=2 随机坐标的区间就是[2,5) 假设是随机到坐标为4 那交换后变成 [2,1,5,4,3]
i=3 随机坐标的区间就是[3,5) 假设是随机到坐标为4 那交换后变成 [2,1,5,3,4]
i=4 随机坐标的区间就是[4,5) 只剩下一个坐标4了,因为区间是不包括5,直接和自己交换后变成 [2,1,5,3,4]
复杂度分析
- 时间复杂度 : O(n)。Fisher-Yates 洗牌算法时间复杂度是线性的,因为算法中生成随机序列,交换两个元素这两种操作都是常数时间复杂度的。
- 空间复杂度: O(n)。因为要实现 重置 功能,原始数组必须得保存一份,因此空间复杂度并没有优化。
type Solution struct {
nums []int
original []int //原始数组
}
//copy复制为值复制,改变原切片的值不会影响新切片。而等号复制为指针复制,改变原切片或新切片都会对另一个产生影响
func Constructor(nums []int) Solution {
original := make([]int, len(nums))
copy(original, nums)
return Solution{original: original, nums: nums}
}
/** Resets the array to its original configuration and return it. */
func (this *Solution) Reset() []int {
return this.original
}
/** Returns a random shuffling of the array. */
func (this *Solution) Shuffle() []int {
len := len(this.nums)
for index,_ := range this.nums {
//每次从index到数组末尾这个区间内随机获取一个数,和index做替换
swapIndex := random(index,len)
this.nums[index],this.nums[swapIndex] = this.nums[swapIndex],this.nums[index]
}
return this.nums
}
// 获取指定区间内的随机数
func random(min, max int) int{
return rand.Intn(max-min)+min
}
/**
* Your Solution object will be instantiated and called as such:
* obj := Constructor(nums);
* param_1 := obj.Reset();
* param_2 := obj.Shuffle();
*/