【代码训练营】day28 | 491.递增子序列 & 46.全排列 & 47.全排列 II

所用代码 java

递增子序列 LeetCode 491

题目链接:递增子序列 LeetCode 491 - 中等

思路

不能排序的情况需去重的话需使用哈希进行去重。
示意图如下:

在这里插入图片描述

使用Set去重: 自带去重功能

class Solution {
    // 使用双向链表LinkedList,可方便移除末尾元素
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    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));
        }
        // 使用set去重
        Set<Integer> set = new HashSet<>();
        for (int i = startIndex; i < nums.length; i++) {
            // 数层去重
            if ((path.size()>0 && nums[i] < path.getLast())
                    || set.contains(nums[i])){
                continue;
            }
            path.add(nums[i]);
            set.add(nums[i]);
            backtracking(nums, i + 1);
            path.removeLast();
        }
    }
}

使用map去重: 原理和set差不多

class Solution {
    // 使用双向链表LinkedList,可方便移除末尾元素
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    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));
        }
        // 使用map去重 定义在此处就是每一层使用
        Map<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);
            path.add(nums[i]);
            backtracking(nums, i + 1);
            path.removeLast();
        }
    }
}

以数组当作哈希表: 本题由于nums的数量是小于等于 200 的,所以我们可以使用一个长度大于200的used数组来代表某个元素已被访问。

class Solution {
    // 使用双向链表LinkedList,可方便移除末尾元素
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    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));
        }
        // 数据量小可使用数组去重 定义在此处就是每一层使用
        // 长度为201,刚好大于nums的最大数量
        int[] used = new int[201];
        for (int i = startIndex; i < nums.length; i++) {
            // 保证递增
            if (!path.isEmpty() && nums[i] < path.getLast()){
                continue;
            }
            // 数层去重
            // +100 的原因是 -100 <= nums[i] <= 100 ,所以需把下标调整到0-201
            if (used[nums[i] + 100] == 1){
                continue;
            }
            path.add(nums[i]);
            used[nums[i] + 100] = 1;
            backtracking(nums, i + 1);
            path.removeLast();
        }
    }
}

总结

本题和前题去重逻辑不同在于本题不能排序,所以不能按前面的方法和前一个数比较然后进行数层去重。但是我们可用哈希的方法记录本层是否出现过这个数,若出现就记录下来,再次出现就可在记录中发现该数。

全排列 LeetCode 46

题目链接:全排列 LeetCode 46 - 中等

思路

排列问题和组合问题是不同的。

排列问题所有元素都要使用,是在叶子结点收集结果,但是不能重复使用,所以用used数组标记

组合问题不一定会用所有的元素,所有用startIndex标记

示意图如下:

class Solution {
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    // used用来标记哪些元素使用过
    int[] used;
    public List<List<Integer>> permute(int[] nums) {
        used = new int[nums.length];
        backtracking(nums);
        return res;
    }
    public void backtracking(int[] nums){
        if (path.size() == nums.length){
            res.add(new LinkedList<>(path));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            // 使用过的元素就不能在使用
            if (used[i] == 1) continue;
            path.add(nums[i]);
            used[i] = 1;
            backtracking(nums);
            path.removeLast();
            used[i] = 0;
        }
    }
}

总结

全排列问题在于每次递归时,每个元素都要去考虑,但是已使用过的元素需要排除,所有就定义了一个used数组来辨别哪些元素时使用过的。

全排列 II LeetCode 47

题目链接:全排列 II LeetCode 47 - 中等

思路

本题是在上题的基础上,多了一步数层去重操作,就和之前的组合2集合2方法一样
示意图如下:

在这里插入图片描述

class Solution {
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    int[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {
        // 初始化used数组,全0
        used = new int[nums.length];
        // 去重记得先排序
        Arrays.sort(nums);
        backtracking(nums);
        return res;
    }public void backtracking(int[] nums){
        if (path.size() == nums.length){
            res.add(new LinkedList<>(path));
            return;
        }for (int i = 0; i < nums.length; i++) {
            // 数层去重
            if (i > 0 && nums[i] == nums[i-1] && used[i-1] == 0){
                continue;
            }
            // 判断该元素是否使用过
            if (used[i] == 1){
                continue;
            }
            path.add(nums[i]);
            used[i] = 1;
            backtracking(nums);
            path.removeLast();
            used[i] = 0;
        }
    }
}

总结

方法万变不离其宗主要就是以下情况:

  • 需要去重

    • 数据能重排 – 重排之后利用uesd数组 used[i-1] == 0 进行数层的去重
    • 数据不能重拍 – 使用set或是map去重,数据少也可以使用used数组
  • 不需要去重

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值