Day31~491.递增子序列、46.全排列、47.全排列 II

161 阅读4分钟

摘要

本文主要介绍了LeetCode回溯算法的几个题目,包括491.递增子序列、46.全排列、47.全排列 II。

1、491.递增子序列

1.1 思路

  • 首先检查路径 path 的长度是否大于等于 2,如果是,就将当前路径添加到结果列表 list 中,因为题目要求至少包含两个元素的递增子序列。
  • 之后,使用了一个 Set 集合 set 来进行横向去重。 在同一层递归中,如果当前元素已经被添加到 path 中,就跳过它,以确保不会有重复的元素。
  • 接下来,检查了递增条件。只有当 path 中的元素为空或者当前元素大于或等于 path 中的最后一个元素时,才将当前元素添加到 path 中,以保持递增。
  • 然后,递归地调用 doFindSubsequences 函数,从下一个位置 i+1 开始搜索可能的递增子序列。
  • 最后,需要回溯,即在完成当前递归后,将 path 中的最后一个元素删除,以便继续搜索其他可能的子序列。

1、为什么90.子集II的横向去重方式在491.递增子序列不行?

90.子集II中我们是通过排序,再加一个标记数组来达到去重的目的。

而本题求自增子序列,是不能对原数组进行排序的,排完序的数组都是自增子序列了。

所以不能使用之前的去重逻辑!

1.2 代码

public List<List<Integer>> findSubsequences(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    doFindSubsequences(list, path, nums, 0);
    return list;
}
​
public void doFindSubsequences(List<List<Integer>> list, LinkedList<Integer> path, int[] nums, int start) {
    if(path.size() >= 2) {
        list.add(new ArrayList<>(path));
    }
​
    Set<Integer> set = new HashSet<>();
    for(int i=start; i<nums.length; i++) {
        // 横向去重
        if(set.contains(nums[i])) {
            continue;
        }
        // 保持递增
        if(path.size() > 0 && nums[i] < path.getLast()) {
            continue;
        }
​
        path.add(nums[i]);
        set.add(nums[i]);
​
        doFindSubsequences(list, path, nums, i+1);
​
        path.removeLast();
    }
}

2、46.全排列

2.1 思路

  • 首先检查路径 path 的长度是否等于输入数组 nums 的长度,如果是,说明已经生成了一个完整的排列,将其添加到结果列表 list 中。
  • 接下来,它使用一个循环遍历 nums 数组中的每个元素。如果当前元素已经在 path 中存在(即 path.contains(nums[i])),则跳过,以确保生成的排列中没有重复的元素。
  • 如果当前元素没有在 path 中存在,它将当前元素添加到 path 中,并递归调用 doPermute 函数以生成下一个元素的排列。
  • 在完成递归之后,它需要回溯,即将 path 中的最后一个元素删除,以便继续生成其他可能的排列。

2.2 代码

解法1:通过判断path中是否存在数字,排除已经选择的数字

    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> list = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        doPermute(list, path, nums);
        return list;
    }
​
    public void doPermute(List<List<Integer>> list, LinkedList<Integer> path, int[] nums) {
        if(path.size() == nums.length) {
            list.add(new ArrayList<>(path));
            return;
        }
​
        for(int i=0; i<nums.length; i++) {
            // 如果path中已有,则跳过
            if (path.contains(nums[i])) {
                continue;
            } 
​
            path.add(nums[i]);
​
            doPermute(list, path, nums);
​
            path.removeLast();           
        }
    }

解法2:used数组

    public List<List<Integer>> permute(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used, false);
​
        List<List<Integer>> list = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        doPermute(list, path, used, nums);
        return list;
    }
​
    public void doPermute(List<List<Integer>> list, LinkedList<Integer> path, boolean[] used, int[] nums) {
        if(path.size() == nums.length) {
            list.add(new ArrayList<>(path));
            return;
        }
​
        for(int i=0; i<nums.length; i++) {
            if (used[i]) {
                continue;
            } 
​
            path.add(nums[i]);
            used[i] = true;
​
            
            doPermute(list, path, used, nums);
​
            path.removeLast();
            used[i] = false;          
        }
    }

3、47.全排列 II

3.1 思路

40.组合总和II

1、为什么i > 0 && nums[i] == nums[i-1] && used[i-1] == false就是树层上的去重?

因为回溯过来取重复的值时,那么前一个值自然是 false,所以i > 0 && nums[i] == nums[i-1] && used[i-1] == false就是树层上的去重

3.2 代码

    public List<List<Integer>> permuteUnique(int[] nums) {
        // 定义used数组
        boolean[] used = new boolean[nums.length];
        Arrays.fill(used, false);
        // 为了将重复的数字都放到一起,所以先进行排序
        Arrays.sort(nums);
        
        List<List<Integer>> list = new ArrayList<>();
        LinkedList<Integer> path = new LinkedList<>();
        doPermuteUnique(list, path, used, nums);
        return list;
    }
​
    public void doPermuteUnique(List<List<Integer>> list, LinkedList<Integer> path, boolean[] used, int[] nums) {
        if(path.size() == nums.length) {
            list.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]使⽤过
            // 如果同⼀树层nums[i-1]使⽤过则直接跳过
            if (i > 0 && nums[i] == nums[i-1] && used[i-1] == false) {
                continue;
            }
            if(used[i]) {
                continue;
            }
​
            path.add(nums[i]);
            used[i] = true;//标记同⼀树⽀nums[i]使⽤过,防止同一树枝重复使用
​
            doPermuteUnique(list, path, used, nums);
​
            path.removeLast();
            used[i] = false;       
        }
    }

参考资料

代码随想录-491.递增子序列

代码随想录-46.全排列

代码随想录-47.全排列 II