LC46 回溯中使用循环的全排列问题|刷题打卡

135 阅读1分钟

本系列使用IDEA+LEETCODE EDITOR插件,题目描述统一英文题目链接
一、题目描述:

//Given an array nums of distinct integers, return all the possible permutations
//. You can return the answer in any order. 
//
// 
// Example 1: 
// Input: nums = [1,2,3]
//Output: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
// Example 2: 
// Input: nums = [0,1]
//Output: [[0,1],[1,0]]
// Example 3: 
// Input: nums = [1]
//Output: [[1]]
// 
// 
// Constraints: 
//
// 
// 1 <= nums.length <= 6 
// -10 <= nums[i] <= 10 
// All the integers of nums are unique. 

二、思路分析:
在之前的这道题 LC78 同样是回溯也有优劣之分的全部子集问题|刷题打卡中,一开始的解我在每次回溯的递归中使用了循环,导致性能很差,修改成每次回溯的递归只最多加入一个值到结果集的方式。 今天我们看一道同样在回溯的递归中使用循环会导致性能非常差,最后优化成高效的题目。 题目描述比较简单,就是一个数组的全排列,也没存在什么坑人的地方,这是比较幸福的题代表你基本都能写出来就是性能优化上要下功夫。
当发现题目描述很清晰也没有什么坑的时候,你应该想到这道题对所有人都是容易写出解的,考官看的是谁能更好的进行优化,能想到这些,这才是本期的重点。

三、AC 代码:
首先看使用了循环的低效代码:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {

        backTrack(0,new ArrayList<Integer>(),nums);
        return res;
    }

    void backTrack(int currentLength,List<Integer> contains,int[] nums){
        if(nums.length == currentLength){
            if (!res.contains(contains)){
                res.add(contains);
            }
            return;
        }
            for (int i = 0; i < nums.length; i++) {
                if(!contains.contains(nums[i])){
                    List<Integer> tmp = new ArrayList<>(contains);
                    tmp.add(nums[i]);
                    backTrack(currentLength +1,tmp,nums);
                }
            }
    }
}
解答成功:
   	执行耗时:11 ms,击败了6.00% 的Java用户
	内存消耗:38.6 MB,击败了75.64% 的Java用户

可以看到性能惨不忍睹,哪怕使用了剪枝。因为这个剪枝是针对一次递归的局部剪枝,并不是全局的,导致大量的重复计算。
开始优化:
我们发现上面解中递归完成的条件判断参数是多余的,可以直接用临时结果集的长度判断,然后全局递归需要一个全局备忘录,记录哪些数字已经被使用了,

    List<List<Integer>> res = new ArrayList<>();
    boolean[] used = null;
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        backTrack(new ArrayList<>(), nums);
        return res;
    }

    void backTrack(List<Integer> contains,int[] nums) {
        if (contains.size() == nums.length) {
            res.add(new ArrayList<>(contains));
            return;
        }

        for (int i = 0; i < nums.length; ++i) {
            if (!used[i]) {
                used[i] = true;
                contains.add(nums[i]);
                backTrack(contains, nums);
                contains.remove(contains.size() - 1);
                used[i] = false;
            }
        }
    }
解答成功:
        执行耗时:1 ms,击败了96.67% 的Java用户
        内存消耗:38.4 MB,击败了96.46% 的Java用户

四、总结:

  1. 题目描述简单的题,要思考如何优化。
  2. 进行好剪枝,回溯里一样可以使用循环。
  3. 逢递归,必剪枝。

本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情