Leetcode:回溯

文章详细介绍了如何使用回溯法解决LeetCode中的子集(包括无重复和有重复元素的情况)和排列(无重复和有重复元素)问题。通过示例代码解析了在搜索过程中如何进行剪枝优化,确保结果的正确性和效率。
摘要由CSDN通过智能技术生成


跟着labuladong刷回溯题的记录。

使用【回溯法】解决【子集、排列】问题:
分为【子集 元素无重复】 【子集 元素有重复】 【排列 元素无重复】 【排列 元素有重复】四类。

子集 无重

leetcode 78

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]

在回溯时传递i:

class Solution {
    List<List<Integer>> res = new LinkedList<>();
    LinkedList<Integer> path = new LinkedList<Integer>();
    public List<List<Integer>> subsets(int[] nums) {
        backtrac(nums, 0);
        return res;
    }
    public void backtrac(int[] nums, int i){
    	//每个节点的值都是一个结果
        res.add(new LinkedList<Integer>(path));
        int n = nums.length;
        for(int j = i; j < n; j++){
            path.addLast(nums[j]);
            backtrac(nums, j + 1);
            path.removeLast();
        }
    }
}

子集 有重

leetcode90

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0]
输出:[[],[0]]

先对元素排序,使相同的元素相邻,搜索时若当前元素和前一元素相同,剪枝当前元素

class Solution {
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backtrack(nums, 0);
        return res;
    }
    public void backtrack(int[] nums, int i){
        res.add(new LinkedList(path));
        for(int j = i; j < nums.length; j++){
            if(j > i && nums[j] == nums[j - 1]){//不是j>0,j>0就剪多了,j>0会导致j=i时也被剪
                continue;
            }
            path.add(nums[j]);
            backtrack(nums,  j + 1);
            path.removeLast();
        }
    }
}

排列 无重

leetcode 46

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]

排除使用过的元素,没有重复元素,只需要排除当前path中已经包含的元素

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

    public List<List<Integer>> permute(int[] nums) {
        backtrack(nums);
        return res;
    }
    public void backtrack(int[] nums){
        if(path.size() == nums.length){
            res.add(new LinkedList<>(path));
            return;
        }
        for(int i = 0; i < nums.length; i++){
            //排序已经包含的元素
            if(path.contains(nums[i])){
                continue; 
            }
            //做选择
            path.add(nums[i]);
            backtrack(nums);
            //撤销选择
            path.removeLast();
        }
    }
}

排列 有重

leetcode 47

​给定一个可包含重复数字的序列 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]]		

先对元素排序,使相同元素相邻。
因为存在相同元素,直接全排列会搜索到[1, 1’, 2]和[1’, 1, 2]的相同结果,去重的方式为:保证相同元素在排列结果中的相对顺序。也就是:当我们搜到第1’时,必须保证1已经使用过,因此若前一相同元素未被使用,剪枝。

class Solution {
    LinkedList<Integer> path = new LinkedList<>();
    List<List<Integer>> res = new LinkedList<>();
    boolean[] used;
    public List<List<Integer>> permuteUnique(int[] nums) {
         Arrays.sort(nums);
         int n = nums.length;
         used = new boolean[n];
         backtrack(nums);
         return res;
    }
    public void backtrack(int[] nums){
        int n = nums.length;
        if(path.size() == n){
            res.add(new LinkedList<>(path));
            return;
        }
        for(int j = 0; j < n; j++){
            if(used[j]){
                continue;
            }
            if(j > 0 && nums[j] == nums[j - 1] && !used[j - 1]){
                continue;
            }
            path.add(nums[j]);
            used[j] = true;
            backtrack(nums);
            path.removeLast();
            used[j] = false;
        }
    }
}

总结

子集

只能选择当前位置后面的元素,通过在回溯时传递搜索的起始索引实现。
若元素有重,先排序,搜索到重复元素(当前元素和前一元素相同)时,剪枝。

排列

不能选择用过的数字,通过判断path中是否存在或者使用used数组记录。
若元素有重,先排序,需要保证相同元素在结果中的相对顺序是一致的,因此:搜索到重复元素(当前元素和前一元素相同)且 前一元素未被使用 时,剪枝。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值