题目描述

给出一组数字,返回该组数字的所有排列 例如: [1,2,3]的所有排列如下 [1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2], [3,2,1]. (以数字在数组中的位置靠前为优先级,按字典序排列输出。)

数据范围:数字个数 0<n60 < n \le 60<n6

题目分析

获取数字的所有排列是一个典型的dfs问题,使用一个列表记录排列的数字,获取排列的过程就是不断在数组中选择数字的过程,从第一个数字开始直到最后一个数字被选择完后,列表即为排列结果,因为数字的排列一共有n!种情况,如图:

alt

因为数字是不能被重复选择的,所以需要记录下之前选过的数字,才能在后面选择中跳过。 dfs模板为:

dfs(int[] num, int start, List<Integer> path) {
 // 递归终止条件
  if(start == num.length){
  		//增加一种排列结果
        return;
  }
  for(int i=0;i<num.length;i++){
  // 对num中的数字进行选择
  		if(num[i]没有被选择过){
            // 选择num[i]
            // 撤销选择
        }
  }
}

解题思路

方法1:dfs递归回溯,使用set记录位置数字是否被访问过,list记录访问的数字

在判断数字是否被访问时,可以使用set来保存访问过的数字,后面在选择时,只要判断set是否包含即可。 不过在做了回溯到上一步的时侯,需要取消选择,set也要删除之前选择的数字。

方法2:dfs递归回溯,使用交换法获得序列

除了基本的一个个数字进行选择方法外,还可以基于原数组,对两个数字位置进行交换得到不同的排列,因为没有重复项数字,只需要遍历所有数字的交换情况即可,交换的思路与选择类似,需要确定要交换的数字位置,不过每次交换不需要都从0的位置开始遍历,前面位置的情况遍历完后,下一次直接从该位置的后一位开始,即在循环中,修改两个地方:

①for循环的 i 初始值为start;

②dfs下一个递归从start+1(作为交换的确定的一个数字位置)后开始;

交换数字后有一个缺点,会导致添加情况的顺序不按照字典序的顺序,可以借助collections.sort()方法进行二维列表的排序。

代码实现

方法1:dfs递归回溯,使用set记录位置数字是否被访问过,list记录访问的数字

    ArrayList<ArrayList<Integer>> res;
    public ArrayList<ArrayList<Integer>> permute(int[] num) {
        Arrays.sort(num);
        res = new ArrayList<>();
        HashSet<Integer> set = new HashSet<>();
        dfs(num, 0, set, new ArrayList<Integer>());
        return res;
    }
     
    public void dfs(int[] num, int start, HashSet<Integer> set, ArrayList<Integer> path){
        if(path.size() == num.length){
            // 枚举了所有数后,将该结果保存
            res.add(new ArrayList<Integer>(path));
            return;
        }
        for(int i=0;i<num.length;i++){
            // 若是已经存储该数字(下标),或者重复的数字中前一个数字没有访问过(说明是以后面为开始访问到前面的数是重复情况)
            if(set.contains(i)){
                continue;
            }
            // 选择数字
            set.add(i);
            path.add(num[i]);
            // set记录从i+1下标开始
            dfs(num, i+1, set, path);
            // 撤销选择
            set.remove(i);
            path.remove(path.size()-1);
        }
    }

时间复杂度:O(nn!)O(n n!)O(nn!),对于数字的排列情况一共有n!种,而在挑选数字的方法中需要对n个数字依次进行遍历,所以时间复杂度为O(nn!)O(n n!)O(nn!)

空间复杂度:O(n)O(n)O(n),在遍历的过程中需要使用额外的set来存储数字下标,最多存储n个数字,同时还需要使用n大小的递归栈空间。

方法2:dfs递归回溯,使用交换法获得序列

    ArrayList<ArrayList<Integer>> res;
    public ArrayList<ArrayList<Integer>> permute(int[] num) {
        Arrays.sort(num);
        res = new ArrayList<>();
        // 默认的数字排列
        ArrayList<Integer> path = new ArrayList<>();
        for(int i=0;i<num.length;i++){
            path.add(num[i]);
        }
        dfs(num, 0, path);
        // 交换位置后的列表,需要重新排序
        Collections.sort(res, new Comparator<ArrayList<Integer>>(){
            public int compare(ArrayList<Integer> list1, ArrayList<Integer> list2){
                for(int i=0;i<list1.size();i++){
                // 依次比较数字大小
                    if(list1.get(i) > list2.get(i)){
                        return 1;
                    }else if(list1.get(i) < list2.get(i)){
                        return -1;
                    }
                }
                return 1;
            }
        });
        return res;
    }
    
    public void dfs(int[] num, int start, ArrayList<Integer> path){
        if(start == num.length){
            // 枚举了所有数后,将该结果保存
            res.add(new ArrayList<Integer>(path));
            return;
        }
        for(int i=start;i<num.length;i++){
            // 交换数字的位置
            swap(path, i, start);
            // 交换需要从start+1处往后
            dfs(num, start+1, path);
            // 撤销交换
            swap(path, i, start);
        }
    }
    
    public void swap(ArrayList<Integer> path, int index1, int index2){
        int tmp = path.get(index1);
        path.set(index1, path.get(index2));
        path.set(index2, tmp);
    }

时间复杂度:O(nn!+n2logn)O(n n! + n^2logn)O(nn!+n2logn),对于数字的排列情况一共有n!种,而在挑选数字的方法中需要对n个数字依次进行遍历,在获得所有的排列后,还需要进行排序(collections默认是合并排序),时间消耗为n2lognn^2lognn2logn

空间复杂度:O(n)O(n)O(n),在遍历的过程中使用了存储所有数字的列表path,后面都在这个列表上进行数据交换,同时还需要使用n大小的递归栈空间。