LeetCode Notes_#46_全排列

LeetCode Notes_#46_全排列

Contents

题目

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

输入: [1,2,3]
输出:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

思路分析

这题的思路是回溯法,或者说DFS深度优先遍历。因为全排列本身就是一种遍历。遍历的就是若干个数字不同顺序的所有可能性。
这个问题可以被抽象为一个决策问题,比如对于123三个数字进行排序,相当于做3次决策,可以画出一个决策树

在这棵树的每个节点位置,有两个属性:

  1. 选择列表:可以选择没有之前没选择过的所有数字作为下一个数字。
  2. 路径:从根节点到当前节点,做出的所有选择组成一条路径,也就是一个全排列。

那么求所有全排列的问题可以转化为遍历这一棵决策树的问题,并且是深度优先遍历。所谓深度优先遍历,有一个很形象的描述:“不撞南墙不回头”,从根节点开始,我们一路向下遍历,直到遇到最底层,不能再往下遍历了,就回头(即结束递归,开始回溯)。

这个问题与普通的树的DFS遍历又有所不同:

  1. 普通的DFS遍历是按照深度优先的顺序输出一个个的节点,但是这题需要的是输出一条条路径。所以我们维护了一个track变量来记录路径。
  2. 由于track变量是作为dfs方法的参数传入,而且track是一个引用变量,所以实际上是所有递归调用函数所共用的。所以我们在保存结果之后,需要进行一步撤销选择的操作,在树中,这一步操作体现为,从当前节点返回到上一层。

下图体现了这一题的整个流程,可以参考。

解答

class Solution {
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        LinkedList<Integer> track = new LinkedList<>();
        dfs(nums, track);
        return res;
    }

    private void dfs(int[] nums, LinkedList<Integer> track){
        //终止条件,当前路径的长度等于数组长度,说明已经是一个全排列,或者说在树中已经遍历到最底层了
        if(track.size() == nums.length){
            //为什么需要使用new?
            //因为这里的track是一个LinkedList的引用变量,自始至终引用的都是同一个内存空间的内容
            //得到一个结果后,需要将其复制到另一块内存空间保存,否则在“撤销选择”的过程中,这个track内容就变化了
            res.add(new LinkedList(track));
            return;
        }
        for(int i = 0;i <= nums.length - 1;i++){
            //可以选择之前没有选过的所有节点
            if(track.contains(nums[i])) continue;
            track.add(nums[i]);
            //开启递归调用,一条路走到底
            dfs(nums, track);
            //撤销选择,回溯到上一层
            track.removeLast();
        }
       
    }
}

复杂度分析

分析过程比较复杂,参考 回溯算法入门级详解 + 练习
时间复杂度:O(n*n!)
空间复杂度:O(n*n!)

参考

原文地址:https://www.cnblogs.com/Howfars/p/13617959.html