【每日算法】打乱数组

229 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情

打乱数组

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

实现 Solution class:

  • Solution(int[] nums):使用整数数组 nums 初始化对象
  • int[] reset(): 重设数组到它的初始状态并返回
  • int[] shuffle(): 返回数组随机打乱后的结果

思路

本题的核心在于 sheffle 方法能产生的所有排列情况都是等可能的。

所有的排列

假设现在有 n 个不同的元素,我们现在对其进行排列。

  • 第一个可以选择 1 ~ n 中的某一个
  • 然后第二次可以选择剩下 n - 1 个中的某一个
  • 依次选择直到最后一个元素。这样的话,排列的总可能数是 n * (n-1) * ... * 1,也就是 !n

sheffle 返回的值是全部等可能中的一种,那么我们是否可以将初始化传入的数组 nums 进行全排列,然后从中随机选取一个呢?

这种方法是可行的,但是对于性能的损耗太大。我们可以对其进行优化。

通过上面的方式,我们很容易知道 n! = n * (n-1)!,相当于我们从 n 个元素中挑取了一个,剩下 n-1 个元素。挑取的方法有 n 种,剩下的 n-1 就是对其他元素的排列。

我们需要一种洗牌方案

  • 等概率的从n个元素中挑选一个作为第一个元素
  • 然后再对剩下的(n-1)个元素作类似的选择
 function shuffle() {
   for(let i=0; i<this.nums.length; i++) {
     let temp // 交换元素的临时参数
     // 题目中的数组长度不超过50,(this.nums.length - 1):每次从剩余的位置中取随机一个
     let target = i + (Math.random() * 100) % (this.nums.length - i) 
     temp = this.nums[i]
     this.nums[i] = this.nums[target]
     this.nums[target] = temp
   }
   return this.nums
 }
  • 交换第一个:先随机交换数组中的第一个元素与其他(包含自己)元素,也就是 arr[0]arr[0] ~ arr[n-1] 都可能发生交换
  • 交换第二个:随机交换 arr[1]arr[1] ~ arr[n-1] 中的一个
  • 直到数组的最后一个值
 var Solution = function(nums) {
     this.nums = nums.slice() // slice 返回一个新数组
     this.origin = nums.slice()
 };
 ​
 Solution.prototype.reset = function() {
     this.nums = this.origin.slice()
     return this.nums
 };
 ​
 Solution.prototype.shuffle = function() {
     for(let i=0; i<this.nums.length; i++) {
         let temp // 交换元素的临时参数
         // 题目中的数组长度不超过50,(this.nums.length - 1):每次从剩余的位置中取随机一个
         let target = i + Math.floor(Math.random() * 100) % (this.nums.length - i)
         // 交换两个元素
         temp = this.nums[i]
         this.nums[i] = this.nums[target]
         this.nums[target] = temp
     }
     return this.nums
 };

\