「LeetCode46」全排列|刷题打卡

203 阅读2分钟

这是力扣第 46 题:

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

例如给定数组 [1, 2, 3],返回其所有可能的全排列,即:

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

当然数也可能是字符串数组,例如 ['I', 'love', 'you'] 的全排列是:

[  [ 'I', 'love', 'you' ],
  [ 'I', 'you', 'love' ],
  [ 'love', 'I', 'you' ],
  [ 'love', 'you', 'I' ],
  [ 'you', 'I', 'love' ],
  [ 'you', 'love', 'I' ]
]

那怎样得到全排列呢?流程如下:

  • 定义结果集 result 和路径集 routes
  • 遍历节点,把不在路径内的新节点放入路径集
  • 递归遍历
  • 删除递归前放入的新节点

代码如下:

function permute(arr) {
  let result = [] // 结果集
  let routes = [] // 路径
  let traverse = () => {
    if (routes.length === arr.length) return result.push([...routes]) // 路径满了放入结果集
    for (let it of arr) {
      if (routes.includes(it)) continue // 如果存在直接跳过
      routes.push(it) // 加入当前值
      traverse() // 递归遍历
      routes.pop() // 删除当前值
    }
  }
  traverse()
  return result
}

可以看到,代码并不复杂,只要掌握了回溯算法的套路,此类题目就可以轻松解决。

套路

然而这一题还有一种变形,就是力扣第 47 题:

给定一个可包含重复数字的序列 nums ,返回所有不重复的全排列。

最直接的想法可能是用上面数字不重复的算法获得全排列之后再去重,然而这样是不可行的,会永远得到空数组。因为上面的算法的前提就是数字不重复,如果数字重复的话,上面的算法就失效了,即下面这个条件永远得不到执行:

if (routes.length === arr.length) return result.push([...routes]) 

那怎么办呢?其实整体框架还是上面回溯的框架,关键是如何跳过重复的值,我们首先要对输入的数组进行排序,然后定义一个 map 来记录路径上已经被使用过的节点,这个时候其实只有两种情况是需要被跳过的:

  • map 标记已使用过,就不能再次使用了,即当前路径上已经选过了同一个数字
  • 同一层的前一个是相同的,但是没被选过的,就不要再选了,因为这种情况前一个已经处理了

借用评论区的一则图解,非常直观:

图解

最终代码如下:

function permuteUnique(arr) {
  arr = arr.sort((a, b) => a - b)
  let len = arr.length
  let result = []
  let routes = []
  let map = {}
  let traverse = () => {
    if (routes.length === len) return result.push([...routes])
    for (let i = 0; i < len; i++) {
      if (map[i] || (i > 0 && arr[i] == arr[i - 1] && !map[i - 1])) continue
      map[i] = true
      routes.push(arr[i])
      traverse(routes)
      map[i] = false
      routes.pop()
    }
  }
  traverse()
  return result
}

AC 截图如下:

本文正在参与「掘金 3 月闯关活动」,点击查看活动详情