代码随想录算法训练营 || 回溯算法 491 46 47

文章介绍了如何使用Java实现力扣(LeetCode)上的三道算法题:491.递增子序列,46.全排列,以及47.全排列II。主要方法是回溯法,重点在于处理递增条件、去重和排列组合的问题。对于递增子序列,关键在于处理重复元素和路径中的元素关系;全排列则需要避免重复选择同一层的元素;全排列II需要在回溯过程中处理重复数字的去重问题。

Day25

491.递增子序列

力扣题目链接

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]

  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

思路

  • 有难度的一道题

  • 需要注意两个点,首先是去重不能简单比较这个元素和上一个是否相等,需要看这一层是否使用过这个元素,如果使用过就直接continue,没有使用过就加入到map中

  • 之后是要比较每个元素和path中加入的上一个元素,如果小就不递增,注意不是比较数组中上一个位置的元素

代码

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

    public List<List<Integer>> findSubsequences(int[] nums) {
        backtracking(nums, 0);
        return res;
    }

    public void backtracking(int[] nums, int startIndex) {
        if (path.size() >= 2) {
            res.add(new ArrayList<>(path));//至少由两个元素才加入res中
        }
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = startIndex; i < nums.length; i++) {
            if (!path.isEmpty() && nums[i] < path.getLast()) {//不递增
                continue;
            }
            if (map.getOrDefault(nums[i], 0) >= 1) {//这层中已有重复元素
                continue;
            }
            map.put(nums[i], map.getOrDefault(nums[i], 0) + 1);//放入map中
            path.addLast(nums[i]);
            backtracking(nums, i + 1);
            path.removeLast();
        }
    }
}

46.全排列

力扣题目链接

给定一个 没有重复 数字的序列,返回其所有可能的全排列。

示例:

  • 输入: [1,2,3]

  • 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

思路

  • 排列问题和组合问题不同,排列问题是有顺序的,因此没法设置startIndex,选了这个元素可能还要回头重新考虑顺序

  • 其实简单了很多,我们每次加入元素的时候看看有没有选过,没选过就加入即可,每次遍历都从第一个元素开始而不是0开始即可

代码

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        if (nums == null || nums.length == 0) return res;
        backtracking(nums);
        return res;
    }
    private void backtracking(int[] nums){
        if (path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++){//每次都从0开始
            if (path.contains(nums[i])) continue;//如果path中已经有了这个元素就continue
            path.addLast(nums[i]);
            backtracking(nums);
            path.removeLast();
        }
    }
}

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]]

思路

  • 排列后注意去重逻辑

  • 同一树枝有两个重复元素,没关系,可以重复选取

  • 但同一数层不能有两个重复元素,我们使用used集合标记

代码

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        if (nums == null || nums.length == 0) return res;
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used,false);//设置used数组,初始化元素为false
        Arrays.sort(nums);//注意把数组排序
        backtracking(nums,used);
        return res;
    }
    private void backtracking(int[] nums, boolean[] used){
        if (path.size() == nums.length){
            res.add(new ArrayList<>(path));//递归结束条件
            return;
        }
        for (int i = 0; i < nums.length; i++){
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue;
            //这个元素和上一个相同不能直接continue,需要保证是同层才能continue
            if (!used[i]){
                used[i] = true;
                path.addFirst(nums[i]);
                backtracking(nums,used);
                path.removeFirst();
                used[i] = false;
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值