[回溯法] -- 46 - 全排列 - python + Java

84 阅读3分钟

解题前先读回溯算法解题套路框架

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

示例:

输入: [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;
        }
    }
}