思路一:
首先处理排列算法的基本情况。如果输入列表为空,则返回一个空列表。如果输入列表只包含一个元素,则返回一个包含该单个元素的列表。这是递归算法的基本条件,它确保算法终止并返回正确的结果。
下面是算法的核心部分,用于生成所有可能的排列。首先,我们创建一个空列表 res
,用于存储最终的排列结果。然后,我们使用一个循环来迭代处理输入列表 nums
中的每个元素。对于每个元素,我们计算出除了当前元素之外的剩余部分 rest
。这是通过将当前元素之前和之后的部分连接而得到的。接下来,我们通过递归调用 self.permute(rest)
来生成剩余部分的所有排列,然后将当前元素附加到每个排列中,形成新的排列。最终,这些新的排列被添加到结果列表 res
中。
最后,一旦所有可能的排列都已生成,我们返回包含所有排列的列表 res
。
代码如下:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if len(nums) == 0:
return []
if len(nums) == 1:
return [nums]
res = []
for i in range(len(nums)):
rest = nums[:i] + nums[i+1:]
for p in self.permute(rest):
res.append([nums[i]] + p)
return res
思路二:
从全排列问题开始理解回溯算法
我们尝试在纸上写 3 个数字、4 个数字、5 个数字的全排列,相信不难找到这样的方法。以数组 [1, 2, 3] 的全排列为例。
- 先写以 111 开头的全排列,它们是:[1, 2, 3], [1, 3, 2],即 1 + [2, 3] 的全排列(注意:递归结构体现在这里);
- 再写以 222 开头的全排列,它们是:[2, 1, 3], [2, 3, 1],即 2 + [1, 3] 的全排列;
- 最后写以 333 开头的全排列,它们是:[3, 1, 2], [3, 2, 1],即 3 + [1, 2] 的全排列;
总结搜索的方法:按顺序枚举每一位可能出现的情况,已经选择的数字在 当前 要选择的数字中不能出现。按照这种策略搜索就能够做到 不重不漏。这样的思路,可以用一个树形结构表示。
先尝试自己画出「全排列」问题的树形结构。
设计状态变量
- 首先这棵树除了根结点和叶子结点以外,每一个结点做的事情其实是一样的,即:在已经选择了一些数的前提下,在剩下的还没有选择的数中,依次选择一个数,这显然是一个 递归 结构;
- 递归的终止条件是: 一个排列中的数字已经选够了 ,因此我们需要一个变量来表示当前程序递归到第几层,我们把这个变量叫做 depth,或者命名为 index ,表示当前要确定的是某个全排列中下标为 index 的那个数是多少;
- 布尔数组 used,初始化的时候都为 false 表示这些数还没有被选择,当我们选定一个数的时候,就将这个数组的相应位置设置为 true ,这样在考虑下一个位置的时候,就能够以 O (1) O (1) O (1) 的时间复杂度判断这个数是否被选择过,这是一种「以空间换时间」的思想。
这些变量称为「状态变量」,它们表示了在求解一个问题的时候所处的阶段。需要根据问题的场景设计合适的状态变量。
代码如下:
from typing import List
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
def dfs(nums, size, depth, path, used, res):
if depth == size:
res.append(path[:])
return
for i in range(size):
if not used[i]:
used[i] = True
path.append(nums[i])
dfs(nums, size, depth + 1, path, used, res)
used[i] = False
path.pop()
size = len(nums)
if len(nums) == 0:
return []
used = [False for _ in range(size)]
res = []
dfs(nums, size, 0, [], used, res)
return res
if __name__ == '__main__':
nums = [1, 2, 3]
solution = Solution()
res = solution.permute(nums)
print(res)