力扣46. 全排列和力扣47. 全排列 II(回溯)

本文解析了LeetCode中的47题全排列II,介绍了如何处理输入数组中重复数字的全排列问题,重点在于避免重复数组和处理选择条件的修改。通过实例和两种方法(标记数组和官方题解的排序技巧)展示了如何解决此问题,适合回溯算法和去重技巧的学习者。
摘要由CSDN通过智能技术生成

46. 全排列 - 力扣(LeetCode) (leetcode-cn.com)

 47. 全排列 II - 力扣(LeetCode) (leetcode-cn.com)

 46题为回溯的经典模板,回溯问题可以理解为输出不同的排列方式,需要注意的是不同的回溯问题添加条件与选择条件不同,需要自己根据不同问题进行coding,这也是回溯问题的困难所在。

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> permute(int[] nums) {
        List<Integer> temp = new ArrayList<>();
        backtrack(res,temp,nums);
        return res;
    }
    public void backtrack(List<List<Integer>> res,List<Integer> temp,int[] nums){
        if(nums.length==temp.size()){
            res.add(new ArrayList<>(temp));
            return;
        }
        for(int i = 0;i<nums.length;i++){
            if(temp.contains(nums[i])){
                continue;
            }
            temp.add(nums[i]);
            backtrack(res,temp,nums);
            temp.remove(temp.size()-1);//注意删掉的永远都是最后一个 千万不能是i i是加入的索引,删掉的不一定是最后一个
        }
    }
}

46题的添加条件为当临时数组(temp)的长度等于原始数组的长度时添加,选择数字的条件是如果当前数字在临时数组(temp)中,那么跳过。

相比与46题,47题的输入可以存在重复数字,且要求输出结果中不能存在重复的数组。

那么我们将47题相较于46题拆分成以上两个问题。

第一个问题:输入数组中存在重复的数字,我们就不能像46题中判断数字是否在临时数组中(因为有重复)来选择数字了。所以我们通过一个标记数组的方式,记录当前数字是否已经添加到临时数组中。

//非题目结果代码,完成了第一个问题
public class num47 {
    boolean[] val;
    public List<List<Integer>> permuteUnique(int[] nums) {
        val = new boolean[nums.length];
        List<List<Integer>>res = new ArrayList<>();
        List<Integer> temp = new ArrayList<>();
        backtrack(nums,res,temp);
        return res;

    }
    public void backtrack(int[] nums,List<List<Integer>>res,List<Integer> temp){
        if(temp.size()==nums.length){
            res.add(new ArrayList<>(temp));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if(val[i]){
                continue;
            }
            temp.add(nums[i]);
            val[i] = true;
            backtrack(nums,res,temp);
            temp.remove(temp.size()-1);
            val[i] = false;
        }
    }

    public static void main(String[] args) {
        num47 test = new num47();
        System.out.println(test.permuteUnique(new int[]{1, 1, 2}));
    }
}

 结果如下图

 第二个问题:去掉重复的数组

1.简单的方式set()

public class num47 {
    boolean[] val;
    public List<List<Integer>> permuteUnique(int[] nums) {
        val = new boolean[nums.length];
        Set<List<Integer>> res = new HashSet();
        List<Integer> temp = new ArrayList<>();
        backtrack(nums,res,temp);
        return new ArrayList<>(res);

    }
    public void backtrack(int[] nums,Set<List<Integer>>res,List<Integer> temp){
        if(temp.size()==nums.length){
            res.add(new ArrayList<>(temp));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if(val[i]){
                continue;
            }
            temp.add(nums[i]);
            val[i] = true;
            backtrack(nums,res,temp);
            temp.remove(temp.size()-1);
            val[i] = false;
        }
    }

    public static void main(String[] args) {
        num47 test = new num47();
        System.out.println(test.permuteUnique(new int[]{1, 1, 2}));
    }
}

 2.官方题解方法

class Solution {
    boolean[] vis;

    public List<List<Integer>> permuteUnique(int[] nums) {
        List<List<Integer>> ans = new ArrayList<List<Integer>>();
        List<Integer> perm = new ArrayList<Integer>();
        vis = new boolean[nums.length];
        Arrays.sort(nums);
        backtrack(nums, ans, 0, perm);
        return ans;
    }

    public void backtrack(int[] nums, List<List<Integer>> ans, int idx, List<Integer> perm) {
        if (idx == nums.length) {
            ans.add(new ArrayList<Integer>(perm));
            return;
        }
        for (int i = 0; i < nums.length; ++i) {
            if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {
                continue;
            }
            perm.add(nums[i]);
            vis[i] = true;
            backtrack(nums, ans, idx + 1, perm);
            vis[i] = false;
            perm.remove(idx);
        }
    }
}

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/permutations-ii/solution/quan-pai-lie-ii-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码实现了这样一个功能,以[1,1,2]为例,当我们处理完第一个输出位置是1的情况时,我们要避免第二个位置上的1再跑到第一个输出位置上的情况。

过程:

[第一个位置的1,第二个位置上的1,2]

第一轮(不考虑递归中的i)i= 0,ans加入[1,1,2],[1,2,1]

第二轮 i = 1 ,题目46中应该输出[1,2,1],[1,1,2]这与上面重复,原因是第一个位置和第二个位置上的数字都是1。所以第二轮不应该输出,直接continue。

第三轮 i = 2,题目46应该输出两个[2,1,1],要避免第二个1和第一1在同一个位置上被选择。

核心选择代码:if (vis[i] || (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1])) {

个人觉得非常抽象,很难理解,看还是能看懂,但是写不出来。建议自己debug感受一下。

反正如果面试出了我肯定用set的方法,这时留下了不学无术的泪水。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值