Day 29 | 回溯:组合、排序、去重逻辑

Day 26

去重:

所谓去重,其实就是使用过的元素不能重复选取。

组合问题可以抽象为树形结构,那么“使用过”在树形结构上有两个维度,一个维度是同一个树枝上使用过,一个维度是同一个数层上使用过。

树层去重:
如何判断同一树层上元素(相同的元素)是否使用过了呢?

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。

此时for循环里就应该做continue的操作。

图解 40.组合总和,如图:
在这里插入图片描述

Day 27

这里是引用

这里主要还是去重的逻辑,跟上面的一样,主要是树层去重,树枝不需要去重

也就是,一个数组内元素可以重复,但是结果中不能有相同的数组。

直接给出代码:

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backtracking(nums, 0);

        return res;
    }
    private void backtracking(int[] nums, int start) {
        res.add(new ArrayList<>(list));
        int[] used = new int[nums.length]; // 只是树层去重
        for (int i = start; i < nums.length; i++) {
            // 去重的逻辑
            if (i > start && nums[i] == nums[i-1] && used[i] == 0) continue;

            list.add(nums[i]);
            used[i] = 1;
            backtracking(nums, i + 1);
            used[i] = 0;
            list.remove(list.size() - 1);
        }
    }
}

Day 28

递增子序列

在这里插入图片描述

这道题其实和上一道 求子集 思路很类似。

不同点在于,递增子序列元素默认不可以排序,子集去重的前提是数组排序后的。这样可以判断相邻元素是否使用过。

所以需要修改去重的逻辑。

同样是树层去重的逻辑,但是数组不一定有序,需要另外一个数据结构记录该层已经使用过的元素,可以使用Set,也可以使用数组。

如:使用Set记录本层中已经使用过的元素,
遍历时,如果本层数组末尾元素大于遍历元素,或者Set中存在该元素,for循环中都应该 continue 这次遍历。

class Solution {

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> list = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {

        backtracking(nums, 0);
        return res;
    }

    private void backtracking(int[] nums, int start) {
        if (list.size() > 1) {
            res.add(new ArrayList<>(list));
        }
        HashSet<Integer> set = new HashSet<>();
        for (int i = start; i < nums.length; i++) {

            if (!list.isEmpty() && nums[i] < list.get(list.size() - 1) || set.contains(nums[i]) ) {
                continue;
            }

            set.add(nums[i]);
            list.add(nums[i]);
            backtracking(nums, i+1);
            list.remove(list.size() - 1);
			// 不需要再对set数组移除元素,应为set定义再回溯函数内,只记录本层的循环,下一层的就重新定义了。
        }

    }
}

全排列

在这里插入图片描述
在这里插入图片描述

排列问题,如果数组无重复元素,比较简单。
回溯时,每次从头遍历,只需要树枝去重即可。
树枝去重,即使用used数组记录元素是否使用过即可。

if (used[i] == 1) continue;

去重都需要排序数组

如果数组中存在重复元素的话,返回不重复的结果。

首选同层使用过的元素需要去重,同时应为排序的原因,每次从数组第一位开始遍历,树枝上就也需要去重操作。

继续使用used数组,

树层去重:

// ...&& used[i-1] == 0 同一层使用过nums[i-1]元素
// ...&& used[i-1] == 1 同一树枝使用过nums[i-1]元素
// 如果同一树层nums[i - 1]使用过则直接跳过
if (i > 0 && nums[i-1] == nums[i] && used[i-1] == 0) {
	continue;
}

if (used[i] == 0) { // 表示 i 位置元素还未使用
	
}

完整代码:

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

        int[] used = new int[nums.length];
        Arrays.sort(nums);
        backtracking(nums, used);

        return res;
    }

    private void backtracking(int[] nums, int[] used) {
        if (path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            // used[i - 1] == true,说明同一树枝nums[i - 1]使用过
            // used[i - 1] == false,说明同一树层nums[i - 1]使用过
            if (i > 0 && nums[i-1] == nums[i] && used[i-1] == 0) continue;

            if (used[i] == 0) {
                used[i] = 1;
                path.add(nums[i]);

                backtracking(nums,used);

                path.remove(path.size() - 1);
                used[i] = 0;
            }

        }
    }
}

使用set数组的话,无法做树枝的遍历去重。因为 set 数组只记录不同元素,相同元素无法被记录,所以从头遍历时无法判断哪个元素已经被使用过了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值