剑指 Offer II 084. 含有重复元素集合的全排列 (47. 全排列 II)(中等 回溯 数组)

剑指 Offer II 084. 含有重复元素集合的全排列 (47. 全排列 II)

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
示例 2:

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

提示:

1 <= nums.length <= 8
-10 <= nums[i] <= 10

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/permutations-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

分析

参考:https://github.com/youngyangyang04/leetcode-master/blob/master/problems/0047.%E5%85%A8%E6%8E%92%E5%88%97II.md
第二种全排列问题,数组中含重复数字,如何去重,需要考虑已使用过的数字和递归的同一树层(同一树枝也可以,但是效率低)中使用过的重复数字。下面是我对 递归的同一树层中使用过的重复数字 去重的理解。
1、 已使用过的数字去重可以利用第一种全排列的方法。对于同层出现的重复元素,代码需要这么理解:假设数组是{1, 2, 1, 1},进行排序后是{1, 1, 1, 2}
2、 对于对递归的同层判断是否有重复元素的代码:
i > 0 && nums[i] = nums[i - 1] && !used[i - 1]
因为 nums[i] = nums[i - 1] && !used[i - 1] 这段代码的存在,我本来认为前面的三个 1 不可能同时存在,这样的话怎么得到正确的结果呢?经过调试我发现,当递归从 i = 0 开始时,最后的 used 数组是 {1, 0, 1, 1} 这样一定得不到 {1, 1, 1, 2} 这个正确结果,那么这层递归进行完了之后,是没有结果输出的(ans.add),接着开始回溯,删除 path 最后的元素,进入下一层递归,因为 nums[i] = nums[i - 1] && !used[i - 1] 这段代码的存在,从 i = 0 开始的递归永远都不可能有结果输出,因为 used 数组的 0 号索引的值一直是1,1号索引的值一直是0;
3、 那么我们可以直接看下一层从 i = 1 开始的递归,初始状态used数组还是 {0, 0, 0, 0} ,经过一轮递归后 used 数组是 {1, 1, 0, 1} 也没有输出,可以发现 used 数组索引 1 处的值永远是 1 ,索引 2 处的值永远是0,也不可能有输出。那么从索引 i = 2 处开始递归呢,used 数组的变化是 {0, 0, 1, 0} -> {1, 0, 1, 0} -> {1, 0, 1, 0} -> {1, 0, 1, 1} 也就是说该层递归的第一轮(i = 0开始)的递归是没有输出的,那么used数组从 i = 0 开始第二个数字(第一个是索引 2 处的数字)就不用考虑了,下面我们来看从 i = 1 开始的第二个数字,used 数组的变化是: {0, 0, 1, 0} -> {0, 1, 1, 0} -> {1, 1, 1, 0} -> {1, 1, 1, 1} ,path 数组为 {1, 1, 1, 2} ,需要注意的是,前面三个1在原数组位置的索引分别为 2 1 0,那么我们可以想象得到:对于另外两个正确的输出 {1, 1, 2, 1} 和 {1, 2, 1, 1},递归开始索引必然还是从 i = 2开始的,used数组的顺序分别是 {0, 0, 1, 0} -> {0, 1, 1, 0} -> {0, 1, 1, 1} -> {1, 1, 1, 1} 和 {0, 0, 1, 0} -> {0, 0, 1, 1} -> {0, 1, 1, 1} -> {1, 1, 1, 1} ,还有最后一个输出 {2, 1, 1, 1} ,显而易见最后三个1的索引也是2 1 0,就不赘述了。
另外需要注意,used 数组不要定义成全局,因为这样不仅会对树层重复元素进行操作判断,还会对树枝重复元素进行操作判断,消耗时间。

题解(Java)

class Solution {
    private List<List<Integer>> ans = new LinkedList<>();
    private LinkedList<Integer> path = new LinkedList<>();

    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        helper(nums, used);
        return ans;
    }

    private void helper(int[] nums, boolean[] used) {
        if (path.size() == nums.length) {
            ans.add(new LinkedList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1] || used[i]) {
                continue;
            }
            path.add(nums[i]);
            used[i] = true;
            helper(nums, used);
            used[i] = false;
            path.removeLast();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值