第七章 回溯算法part05 491. 递增子序列 46. 全排列 47. 全排列 II

第二十九天| 第七章 回溯算法part05 491. 递增子序列 46. 全排列 47. 全排列 II

一、491. 递增子序列

  • 题目链接:https://leetcode.cn/problems/non-decreasing-subsequences/

  • 题目介绍:

    • 给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

      数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

  • 思路:

    • 外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
    • 本题有三个关键点:
      • (1)什么时候什么情况下收集结果集?
        • 和子集问题一样,在遍历每个节点的开始位置,收集结果集
        • 但是与自己问题不同的是,并不是所有的都可以添加
        • 必须是路径**path中元素个数大于等于2的**
      • (2)怎么判断这个子序列是递增的?也就是什么情况下会去掉不满足递增条件的子序列
        • 如果path的末尾的元素数值大于待取元素数值,说明此时不满足递增条件
      • (3)如何去重?
        • 去重依旧是定位在一个集合,即树层去重
        • 但是由于此时不能排序,因此需要定义一个哈希表记录加入的元素
        • 如果待取元素在哈希表中出现过,说明重复了,要去重
    • 根据上面的思路以及回溯三部曲,整理为以下:
      • (1)确定参数和返回值
        • 因为是在同一个数组中遍历,所以仍然需要startIndex
      • (2)递归结束的条件
        • 在递归结束条件之前收集结果集
        • startIndex >= nums.length
      • (3)单层搜索的逻辑
        • (3.1)确定需要去掉的情况
          • (3.1.1)去掉不满足递增的树枝:如果数组中待取的元素(即:nums[i])小于当前path末尾的元素, 需要去掉(因为不满足递增) 因为要使用path.size()所以要求其不为空
          • (3.1.2)去重:依旧是定位在一个集合上,但是由于不能排序,所以需要定义一个set存放取出的数据,如果后续遍历当前集合的时候,发现set中出现了该值,就跳出该循环
        • (3.2 )处理节点
        • (3.3)递归
        • (3.4)回溯
  • 代码:

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        backtracking(nums, 0);
        return result;
    }

    // 1.确定参数和返回值
    // 因为是在同一个数组中遍历,所以仍然需要startIndex
    public void backtracking(int[] nums, int startIndex) {
        // 收集结果集和子集问题一样,在每个节点处收集
        if (path.size() >= 2) {
            result.add(new ArrayList<>(path));
        }
        // 2.递归结束的条件
        if (startIndex >= nums.length) {
            return;
        }
        // 3.单层搜索的逻辑
        HashSet<Integer> set = new HashSet<>();
        for (int i = startIndex; i < nums.length; i++) {
            // 3.1 确定需要去掉的情况
            // 3.1.1 去掉不满足递增的树枝:如果数组中待取的元素(即:nums[i])小于当前path末尾的元素, 需要去掉(因为不满足递增) 因为要使用path.size()所以要求其不为空
            // 3.1.2 去重:依旧是定位在一个集合上,但是由于不能排序,所以需要定义一个set存放取出的数据,如果后续遍历当前集合的时候,发现set中出现了该值,就跳出该循环
            if (!path.isEmpty() && nums[i] < path.get(path.size() - 1) || set.contains(nums[i])) {
                continue;
            }
            // 3.2 处理节点
            path.add(nums[i]);
            set.add(nums[i]);
            // 3.3 递归
            backtracking(nums, i + 1);
            // 3.4 回溯
            path.removeLast();
        }
        return;
    }
}

二、46. 全排列

  • 题目链接:https://leetcode.cn/problems/permutations/

  • 题目介绍:

    • 给定一个**不含重复数字**的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
  • 思路:

    • 区分排列和组合的区别
      • 对于集合[1, 2]和[2, 1]
        • 组合是一样的
        • 排列是不同的
    • 在代码中,我们不需要startInde控制下一次循环是从什么位置开始,我们每次从数组的index = 0的位置开始,只需要跳过已经记录在path中的节点即可
  • 代码:

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        backtracking(nums);
        return result;
    }

    // 1.确定返回值和参数
    // 因为排列和组合不一样,如果两哥元素顺序不同表示不同的排列,但是是相同的组合
    // 我们每次都是从数组的开始元素去取值,只需要跳过取过的值即可
    public void backtracking(int[] nums) {
        // 2.确定终止条件
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        // 3.单层递归的逻辑
        for (int i = 0; i < nums.length; i++) {
            // 3.1 跳过已经收集进来的节点
            if (path.contains(nums[i])) {
                continue;
            }
            // 3.2 处理节点
            path.add(nums[i]);
            // 3.3 递归
            backtracking(nums);
            // 3.4 回溯
            path.removeLast();
        }
        return;
    }
}

三、47. 全排列 II

  • 题目链接:https://leetcode.cn/problems/permutations-ii/

  • 题目介绍:

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

    • 因为数组中存在重复元素,并且要求解集中不能重复,所以要进行去重(去重还是先排序【一般的去重都要排序,除了“递增子序列”比较特殊】)
    • 在树枝和树层中,都存在nums[i - 1] == nums[i]的情况,我们要进行树层去重
    • 怎么定位到树层呢?
      • 因为在树枝中,前一个和后一个数值可以相当,并且前后都取出来了,所以used[i - 1] 和 used[i]均为true
      • 但是在树层中,use[i - 1] = false,因为前一个在此次循环中并没有取出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 代码:
class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        // 将used数组初始值全部赋值为false
        // 取出了就变为true
        Arrays.fill(used, false);
        // 排列:去重的关键步骤
        Arrays.sort(nums);
        backtracking(nums, used);
        return result;
    }

    // 1.确定返回值和参数
    // 需要传入一个used数组记录元素的取出情况
    public void backtracking(int[] nums, boolean[] used) {
        // 2.结束递归的条件
        if (path.size() == nums.length) {
            result.add(new ArrayList<>(path));
            return;
        }
        // 3.单层递归的逻辑
        for (int i = 0; i < nums.length; i++) {
            // 3.1 去重
            // 树层去重
            if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == false) {
                continue;
            }
            // 3.2 对于未取出的元素进行节点处理
            if (used[i] == false) {
                path.add(nums[i]);
                used[i] = true;
                // 3.3 递归
                backtracking(nums, used);
                // 3.4 回溯
                path.removeLast();
                used[i] = false;
            }
        }
        return;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值