前端刷题路-Day62:全排列(题号46)

708 阅读1分钟

这是我参与更文挑战的第26天,活动详情查看: 更文挑战

全排列(题号46)

题目

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

链接

leetcode-cn.com/problems/pe…

解释

这题啊,这题是经典排列组合。

还是比较简单的一题,只要考虑所有的组合就可以了,而且题目还规定了没有重复数字,这就更简单了。

官方推荐是用递归做的,每次需要注意当前数组的长度,和数字的使用状态(用过还是没用过),之后持续回溯就好,回溯的过程中注意要更新数字的使用状态,否则会导致数字使用重复或者压根不使用的情况出现。

但笔者这里并不推荐这种方法,因为相对来说还是比较复杂的,时间复杂度应该来到了O(n2),有没有一种更简单的方法呢?

这就是笔者第一时间想到的方法了,是的,笔者第一时间并没有想到回溯,而是简单粗暴的列出所有的可能性。

比方说这样的一个🌰:

[1, 2, 3]

首先拆解数组,可以分为这样的三个数组👇:

[1]
[1, 2]
[1, 2, 3]

第一个数组的话就很简单了,只有一种情况,那就是:

[1]

如果再添加一个数组呢?应该是两种情况👇:

[1, 2]
[2, 1]

这里其实是有个特征的,2这个数字是插入在1的前面和后面的,按照index来说的就是01

如果再有一个数字呢?

那就可以插入三个位置了,分别是012

也就是插入在长度的为2的数组中的头部,中间和尾部,或者也可以说插入在所有的缝隙中。

说到这里是不是豁然开朗了,是的,思路就是这么简单。

每次只需要将新的数字插入到已经存在的数组中的每个位置,生成新数组后记录下来即可,一直到最后数字用完了,那么此时就可以拿到所有的可能性了。

自己的答案(排列组合)

话不多说,看看代码👇:

var permute = function(nums) {
  var res = [[nums[0]]]
  nums.shift()
  for (const num of nums) {
    var len = res.length - 1
    while (len >= 0) {
      var arr = res.shift()
      for (let i = 0; i <= arr.length; i++) {
        arr.splice(i, 0, num)
        res.push([...arr])
        arr.splice(i, 1)   
      }
      len--
    }
  }
  return res
};

首先是初始化的赋值,由于题目规定了nums的长度至少为1,那就直接将nums中的第一个元素插入到res中,同时去掉nums中的第一个元素。

接下来开始循环剩余的nums,每遇到一个新的数字,都将其插入到res中所有数组的所有缝隙中去,这里有一点需要注意,由于数组是复杂对象,每次都是指向同一个内存地址,所以每次插入完新数字后要把这个数字再擦出掉,否则会出现数字重复的情况。

最后直接返回res就行,这就是最后的答案了。

普通的解法(回溯)

回溯在上一题中刚刚说过,这里也不重复回溯的定义了。

主要的回溯逻辑就是记住当前数组的长度和所有数字的状态(用没用过),之后就可以不断调用回溯方法,完成解答👇:

var permute1 = function(nums) {
  var res = []
      obj = {}
  function DFS(arr) {
    if (arr.length === nums.length) return res.push([...arr])
    for (const num of nums) {
      if (obj[num]) continue
      obj[num] = true
      DFS(arr.concat([num]))
      obj[num] = false
    }
  }
  DFS([])
  return res
};

在回溯方法中,第一行是回溯的终止条件,也就是当数组长度和nums长度一样时,证明回溯已经到头了,已经是答案了,直接推入res中即可。

这里还是要注意同样的问题,因为数组都是指向同一个内存地址,所以此处使用解构赋值来生成一个新数组推入到res中,否则后序数组被修改会影响到之前到元素。

终止条件后就开始循环了,按照数字顺序来一点点进行操作,如果当前数字被用过了,就跳过了。

如果没被用过,就将其状态改为用过,然后在数组中添加当前数字,进行下一次递归。最后别忘了将数字状态重置为未使用,否则会出现数字缺失的情况。

这种方法的性能着实很差,时间和空间都在30%到40%左右,而如果使用排列组合解法,则二者都可以到达90%,足以证明官方提供的解法有时候并不是最优了,依然有提升的空间。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)

有兴趣的也可以看看我的个人主页👇

Here is RZ