LeetCode 46. 全排列(中)
本题的回溯函数,使用第一个整数的索引作为参数 backtrack(first)。
递归实现每个位置的数字都能和它后面的所有数字交换的,一共有 \(n!\) 种。
题目
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例
1 |
|
考察知识点
回溯算法
1 |
|
核心思想
方法一、回溯法(基于递归交换)
回溯法是一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认 不是 一个解的话(或者至少不是 最后一个 解),回溯算法会通过在上一步进行一些变化抛弃该解,即 回溯 并且再次尝试。
本题的回溯函数,使用第一个整数的索引作为参数 backtrack(first)。递归实现每个位置的数字都能和它后面的所有数字交换的,一共有 \(n!\) 种。
算法
- 如果索引
first
等于n
,意味着当前排列已完成,first
就是当前要探索的范围的开始处。 - 遍历索引
first
到索引n - 1
的所有整数。- 在排列中放置第
i
个整数,即swap(nums[first], nums)
- 继续生产从第
i
个整数开始的所有排列:backtrack(first + 1)
- 现在回溯,即通过
swap(nums[first], nums[i])
还原。
- 在排列中放置第

方法二、回溯算法(基于额外空间)
算法
- 声明
res
、length
、used
等全局变量,其中used
用于保存每个数字的使用情况。 - 开始回溯
- 终止条件判断,终止条件是寻找深度达到目标深度
- 将
path
添加到res
中,注意,由于list是可变对象,因此,这里要用[:]取到具体的值,再append进res中。res.append(path[:])
return
- 将
- 遍历所有待添加数,开始选择。
- 如果当前数字没有使用
- 做选择:将当前数字添加到
path
中,并更新used
为True
。 - 继续深度优先遍历
backtrack(path, depth + 1)
- 撤销选择:更新
used
为False
,path
弹栈,弹出刚刚加进去的那个数字。
- 做选择:将当前数字添加到
- 如果当前数字没有使用
- 终止条件判断,终止条件是寻找深度达到目标深度

进一步改进
布尔数组在这题里的作用是判断某个位置上的元素是否已经使用过。有两种等价的替换方式:
(1)哈希表;
(2)位掩码,即使用一个整数表示布尔数组。此时可以将空间复杂度降到 O(1)O(1)(不包括 path 变量和 res 变量和递归栈空间消耗)。
如何使用位掩码来表示元素使用情况呢?
具体解释,可以参看本题语法糖总结部分。
Python版本
方法一的实现
1 |
|
方法二的实现
1 |
|
时间复杂度:\(O(N×N!)\)
空间复杂度:\(O(N×N!)\)
执行用时 :48 ms, 在所有 Python3 提交中击败了46.34%的用户
内存消耗 :13.6 MB, 在所有 Python3 提交中击败了24.90%的用户
方法二的字符掩码版本
1 |
|
时间复杂度:\(O(N×N!)\)
空间复杂度:\(O(N×N!)\)
执行用时 :32 ms, 在所有 Python3 提交中击败了98.16%的用户
内存消耗 :13.7 MB, 在所有 Python3 提交中击败了24.67%的用户
有效语法糖
1、使用字符掩码代替哈希表、布尔数组,记录不同元素的情况,实现空间复杂度为 \(O(1)\)。
布尔数组的表示也可以通过一个整数的二进制表示,在每个数位上非 0 即 1,这就可以表示一个布尔型数组。它们的区别仅在于:
1). 二进制右边是低位,数组左边是索引为 0 的位置;
2). 一个整数的二进制有 32 位,不过回溯搜索的问题复杂度基本上都很高,本题是 O(n!) ,n = 32 的时候已经非常大了,一般来说测试用例都达不到这个级别。
因此,完全可以用一个整数表示一个布尔型数组。而我们对布尔型数组的操作不外乎就两个:
1). 把某个索引位置从 true 变为 false。
2). 把某个索引位置从 fasle 变为 true。
异或操作,是不进位的加法,一个数位异或上 1 以后,它的功效就是使得 1 变 0 ,0 变 1。因此就可以通过对整数进行异或运算达到操作布尔型数组的效果。
1 |
|
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!