LeetCode 46 全排列

280 阅读2分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

题目:给定一个无重复的数组nums,返回其所有可能的全排列。

解题思路

本题参考题解,成功率七十多,我是废物!题解看了半天有了点点感觉,下面介绍一下:

本题解决方法为使用回溯法,回溯实际上就是一种暴力解法,通过一步步试错,找到最终正确的答案。本题要求找到一个数组的全排列,一种很直观的思想就是从前往后多重遍历数组,使用一个列表来储存每次遍历的合适的值,合适的值就是指那些不在当前列表的,最终当列表的长度为当前数组的长度时,这个解就满足。如何获取一个合适的值很简单,可以新增一个和当前数组等长度的visited数组,数组i上的元素代表第i个元素是否已经在列表中,在每次遍历的时候,我们可以查询当前元素在visited数组中的情况来选择加入还是跳过。

我们可以使用递归来解决问题,借用leetcode题解中的一张图:

image-202202011543159772.png

我们首先设置全局变量list来作为最终结果,使用局部变量arr列表存储中间过程的结果,初始中间结果为空,创建boolean数组来存储数组元素的使用情况,之后调用回溯函数,回溯函数的过程为:

  1. 判断当前arr列表长度,如果长度等于数组长度则全局变量add当前列表,程序结束。
  2. 遍历数组,判断数组中每个元素的使用情况,如果未使用则add进局部变量,之后设置已使用,调用回溯算法,之后删除列表最后一个元素,设置未使用,函数回溯。
  3. 递归调用上述过程即可。

上面那个图是精髓!

代码如下:

private ArrayList<List<Integer>> result = new ArrayList<>();

    public List<List<Integer>> permute(int[] nums) {
        int n = nums.length;
        boolean[] visited = new boolean[n];
        ArrayList<Integer> arr = new ArrayList<>();
        backtrace(arr, visited, nums);
        return result;
    }

    public void backtrace(ArrayList<Integer> arr, boolean[] visited, int[] nums){
        if(arr.size()==nums.length){
            result.add(new ArrayList<>(arr));
        }
        for(int i=0;i<nums.length;i++){
            if(!visited[i]){
                arr.add(nums[i]);
                visited[i] = true;
                backtrace(arr, visited, nums);
                arr.remove(arr.size()-1);
                visited[i] = false;
            }
        }
    }

上述代码存在一个需要注意的地方:

	result.add(new ArrayList<>(arr));

此处如果写成:

	result.add(arr);

则会得到所有结果的空列表。这是因为Java中列表存储的是内存地址,当向一个列表add另一个列表的时候,add的是那个列表的引用,当对列表进行操作的时候,已经被add的列表也会受影响。而回溯则会返回上一个结点,因此会得到全部为空的结果。该算法时间复杂度和空间复杂度都为O(NN!)O(N * N!)