解题前先读: 回溯算法解题套路框架
给定一个没有重复数字的序列,返回其所有可能的全排列。
示例:
输入: [1,2,3]
输出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
题目要求返回给定无重复元素数序列所有可能的全排列,那么就需要穷尽考虑所有可能的情况,所以选择使用用回溯法。
如果将题目中的示例所有可能的情况画成一棵决策树的形式,如下所示:
其中每个节点都需要维护两类信息:
- 当前的路径
- 选择列表
例如,根节点为初始节点,因此,此时的路径为空,选择列表就是全部的1,2,3。当走到[2],[1,3]节点时,由于2已经在当前的路径中,所有可供选择的就只有1和3。最后,当每个分支都走到底时,此时的选择列表已经都为空,无法再做出选择,算法终止。
因此,在解这道题之前我们需要掌握三个核心的信息:
- 路径:某个节点之前已经做出的所有选择
- 选择列表:除去已经做出的选择外,当前还可以做出哪些选择
- 结束条件:选择列表为空
Python的解题代码如下:
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if nums is None: return None
if len(nums) == 1: return [nums]
# 记录所有可能的选择
results = []
# nums: 选择列表
# index:当前已有的元素个数
# track:当前路径
def backtrack(track, nums):
# 如果此时选择列表为空,说明已经到达叶子节点,返回结果
if nums == []:
results.append(track.copy())
return
# 依次选择nums中的元素
for i in range(len(nums)):
# 做出选择
track.append(nums[i])
# 继续往下走
backtrack(track, nums[:i] + nums[i + 1:])
# 撤销选择
track.pop()
# 回溯
backtrack([], nums)
return results
代码运行流程图:
或者使用一个布尔列表来标记当前元素是否访问过,只有当当前元素未被访问过,才可以加入到路径中。
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
if nums is None: return None
if len(nums) == 1: return [nums]
results = []
def backtrack(track, nums, vis, index = 0):
if index == len(nums):
results.append(track.copy())
return
for i in range(len(nums)):
# 只看之前没有选择过的情况
if vis[i] == False:
# 做出选择
vis[i] = True
track.append(nums[i])
backtrack(track, nums, vis, index + 1)
# 撤销选择
vis[i] = False
track.pop()
# 定义访问列表,表示访问过的元素就不能再用
vis = [False] * len(nums)
# 递归函数
backtrack([], nums, vis, 0)
return results
2020 - 8 - 1 更新Java解题代码, 如下所示
/**
* @Author dyliang
* @Date 2020/8/1 22:40
* @Version 1.0
*/
public class Solution1 {
static List<List<Integer>> res = new LinkedList<>();
public static void main(String[] args) {
int[] nums = {1,2,3};
permute(nums);
res.forEach(System.out :: println);
}
// 路径:记录在 track 中
// 选择列表:nums 中不存在于 track 的那些元素
// 结束条件:nums 中的元素全都在 track 中出现
public static List<List<Integer>> permute(int[] nums) {
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums, track);
return res;
}
public static void backtrack(int[] nums, LinkedList<Integer> track) {
// 触发结束条件
if (track.size() == nums.length) {
res.add(new LinkedList(track));
return;
}
for (int i = 0; i < nums.length; i++) {
// 排除不合法的选择
if (track.contains(nums[i]))
continue;
// 做选择
track.add(nums[i]);
// 进入下一层决策树
backtrack(nums, track);
// 取消选择
track.removeLast();
}
}
}
或者同样使用一个布尔数组来指明某个元素是否被访问过,代码如下:
class Solution {
public List<List<Integer>> permute(int[] nums) {
LinkedList<Integer> track = new LinkedList<>();
List<List<Integer>> res = new LinkedList<>();
boolean[] vis = new boolean[nums.length];
backtrack(nums, track, vis, 0, res);
res.forEach(System.out :: println);
return res;
}
public void backtrack(int[] nums, LinkedList<Integer> track, boolean[] vis, Integer count, List<List<Integer>> res) {
if (count == nums.length) {
res.add(new LinkedList<>(track));
return;
}
for (int i = 0; i < nums.length; i++) {
if (vis[i]) {
continue;
}
// 先添加再设置标识位
track.add(nums[i]);
vis[i] = true;
backtrack(nums, track, vis, count + 1, res);
// 先撤销选择再设置标识位
track.removeLast();
vis[i] = false;
}
}
}