leetcode 刷题_day31_回溯05_子集基于哈希表去重&排列及去重重点

文章介绍了如何通过回溯算法解决递增子序列和全排列问题,强调了数组顺序的重要性、去重策略以及如何利用哈希表优化。同时,讨论了两种问题的时间复杂度均为O(n)和空间复杂度为O(n)。
摘要由CSDN通过智能技术生成

491.递增子序列

思路

整体是通过回溯来解决子集问题

  1. 本题数组的顺序不能改变,否则无法通过递归返回原顺序
  2. 同层需要去重,纵向不用去重 – > 完全相同结果要去重,数组内相同元素不去重
  3. 因为数组需要保持原顺序,同层去重无法通过前后对比实现,所以要借助哈希表

image.png

解题方法

注意:

  1. 数值范围有限,可以使用数组来替代 set 实现哈希表的作用
  2. 终止条件注意有两个:叶子节点和不符合条件的节点

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: O ( n ) O(n) O(n)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

Code


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

    // 整体是通过回溯来解决子集问题
    // 如果使用回溯来解决,则数组的顺序不能改变,否则无法通过递归返回原顺序
    // 同层需要去重,纵向不用去重 -- > 也即,完全相同结果要去重,数组内相同元素不去重
    // 因为数组需要保持原顺序,同层去重无法通过前后对比实现,所以要借助 set
    public List<List<Integer>> findSubsequences(int[] nums) {
        backTracking(nums, 0);
        return result;
    }

    private void backTracking(int[] nums, int startIndex) {
        // 终止条件
        // 1. 到了叶子节点
        // 2. 纵向上已经到了不符合条件的节点,也即当前节点值小于父节点值
        if (nums.length < startIndex) {
            return;
        }
        if (path.size() >= 2 && path.get(path.size() - 2) > path.get(path.size() - 1)) {
            return;
        }

        // 收集子集
        if (path.size() >= 2) {
            result.add(new ArrayList<>(path));
        }

        // 横向遍历
        // 记录当前层级已经用过的数值
        // HashSet<Integer> used = new HashSet<>();
        // 已知数值范围,可以直接用数组来模拟哈希表,提高效率
        int[] used = new int[201];
        for (int i = startIndex; i < nums.length; i++) {
            if (/*used.contains(nums[i])*/used[nums[i] + 100] == 1) {
                continue;
            }
            path.add(nums[i]);
            // used.add(nums[i]);
            used[nums[i] + 100] = 1;

            backTracking(nums, i + 1);

            path.removeLast();
        }
    }
}

46.全排列

思路

排列不同于组合,[1, 2] 和 [2, 1] 对组合是同一个答案,在处理时要排除;但对于排列就是不同的两个答案

  1. 组合通过在纵向遍历中传递 startIndex 来控制下一层级从上一层级的后面开始横向遍历,以此保证组合层面的唯一性
  2. 排列通过 used 数组,保证在纵向方向上能完整遍历整个数组,而在横向方向上,除了各自父及祖先节点不要,其他的都要遍历

image.png

解题方法

回溯模板配合 used 数组使用

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: O ( n ) O(n) O(n)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

Code


  class Solution {

    private List<List<Integer>> result = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();
    boolean[] used;

    // 排列不同于组合,[1, 2] 和 [2, 1] 对组合是同一个答案,在处理时要排除;但对于排列就是不同的两个答案
    // 组合通过在纵向遍历中传递 startIndex 来控制下一层级从上一层级的后面开始横向遍历,以此保证组合层面的唯一性
    // 排列通过 used 数组
    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        backTracking(nums);
        return result;
    }

    private void backTracking(int[] nums) {
        // 终止条件--叶子节点
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }

        // 横向遍历
        // 父节点及祖先节点用过的索引不用,其余的都要
        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue;
            }

            used[i] = true;
            path.add(nums[i]);

            backTracking(nums);

            used[i] = false;
            path.removeLast();
        }
    }
}

47.全排列 II

思路

同一层级的去重,这里不在意顺序,可以先排序

注意:
和组合的同一层级去重一样,去重只针对当前层级可用的数值,不能带入之前层级已经用过(本层级不可用)的数值

image.png

解题方法

描述你的解题方法

复杂度

  • 时间复杂度:

添加时间复杂度, 示例: O ( n ) O(n) O(n)

  • 空间复杂度:

添加空间复杂度, 示例: O ( n ) O(n) O(n)

Code


  class Solution {
    private List<List<Integer>> result = new ArrayList<>();
    private LinkedList<Integer> path = new LinkedList<>();
    private boolean[] used;

    // 同一层级的去重,这里不在意顺序,可以先排序
    // 和组合的同一层级去重一样,当前节点去重判断要特别注意,一定要忽略之前层级的数值,否则会过滤掉应有的结果
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        used = new boolean[nums.length];
        backTracking(nums);
        return result;
    }

    private void backTracking(int[] nums) {
        if (nums.length == path.size()) {
            result.add(new ArrayList<>(path));
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            if (used[i]) {
                continue;
            }

            // 统一层级去重
            // 注意:去重只针对当前层级可用的数值,不能带入之前层级已经用过(本层级不可用)的数值
            if (i > 0 && !used[i - 1] && nums[i] == nums[i - 1]) {
                continue;
            }

            path.add(nums[i]);
            used[i] = true;

            backTracking(nums);

            used[i] = false;
            path.removeLast();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值