题目一
解法一:回溯法(dfs实现)
这个问题可以看作有 n 个排列成一行的空格,我们需要从左往右依此填入题目给定的 n 个数,每个数只能使用一次。那么很直接的可以想到一种穷举的算法,即从左往右每一个位置都依此尝试填入一个数,看能不能填完这 n 个空格,在程序中我们可以用「回溯法」来模拟这个过程。
定义递归函数 backtrack(index,path)表示当前从左往右填到第 index个位置上的排列结果为 path。整个递归函数分为两种情况:
- 若 index == n,说明我们已经填完了 n个位置(下标从 0开始,故 index = n时已经走到了数组末尾之外),即找到了一个可行解,将当前的排列结果 path 加入到 res中,递归结束。
- 如果 index < n,这时要考虑第 index个位置要填哪个数,根据题意我们肯定不能填已经填过的数字,所以很容易想到的一个解决思路是借助一个标记数组 visited[] 来标记已经填过的数字,这样一来,当我们在填第 index个位置时,遍历题目给定的 n个数,如果这个数字还没有被标记过,我们就尝试填入,并标记为已访问,然后继续尝试填下一个位置,即调用递归函数 backtrack(index+1,path)。当递归函数执行完回溯的时候,我们要撤销这个位置填的数及其标记,因为只有撤销了当前的选择,下一次搜索另一种排列结果时才有数可以选,这也正是回溯法的思想。
优化空间复杂度:标记数组需要申请额外的内存空间,那么有没有方法可以去掉这个标记数组?
我们可以借鉴排序算法中常常采用的原地置换方法,将含有 n个元素的数组 nums[]划分成两部分,左边是已经填过的数字,右边是待填的数字,我们在递归搜索的过程中只要动态维护这个数组即可。
具体来说,假设当前我们已经填到第 index个位置了,那么 nums[]中下标 0 ~ index-1 的位置上的元素均是已经填过的数,下标 index ~ n-1表示代填的数的集合,那么此时我们肯定是要从后半部分即 nums[index , n-1]中去寻找一个元素填入到当前 index的位置上,假设待填入的数字所在的下标为 i,那么填完之后我们将第 i个数和第 index个数交换,这样一来就能使得下一次在填第 index+1个位置时,nums[0 , index]均是已经填过的数字了,而nums[index+1 , n-1]为待填的数。然后,回溯的时候我们只要将这两个数交换回来即可撤销当前的选择。
不过这个方法有个局限性,那就是这样生成的全排列并不是按字典序存储在答案数组中的,如果题目要求按字典序输出,那么请还是用标记数组或者其他方法。
Python
class Solution(object):
def permute(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
n = len(nums)
if n == 0: return []
self.res = []
self.backtrack(nums, index=0, path=[])
return self.res
def backtrack(self, nums, index, path):
# 满足结束条件,将当前搜索出的排列结果添加到 res中
# 由于 Python中list是可变对象,在递归中是全程只使用一份内存空间的,在dfs结束后,path就会退回到递归树的根结点,也即恢复一开始传入的空 []
# 因此,当满足条件时,应该添加的是当前 path的一份拷贝,直接 res.append(path)添加的只是 path的引用,会受到后续 path中元素变换的影响
if index == len(nums):
self.res.append(path[:]) # 使用切片[:]操作相当于浅拷贝,也可以显示调用copy.copy()函数
return
for i in range(index, len(nums)):
# 做选择
path.append(nums[i])
nums[index], nums[i] = nums[i], nums[index]
# 递归搜索