打卡round1/day4&5【补】/回溯算法3-子集、全排列

1. 关于回溯

tbc

回溯的应用场景

  1. 组合
  2. 分割
  3. 子集
    • 要求:给你一个整数数组 nums ,返回该数组所有可能的子集(幂集)。解集不能包含重复的子集
    • 思路:path不再是各节点组成的路线,而是每个节点储存的组合
  4. 全排列
    • 要求:给定一个数组 nums ,返回其所有可能的全排列
    • 思路:
  5. 差异:
    • 需要return的path是每个节点还是多个节点组成的路径?
    • 递归剩余的nums,是nums[i]序列之后的部分,还是除了nums[i]的所有其余部分?
    • 怎么去重?
    • 待施工
  6. 待强化:
    • 需要巩固的:arraylist, linkedlist到底有什么不同?在数据结构上,在方法调用上。|| 哈希表和hashset的不同?

2. 例题

lc78 子集I

思路

参数:老三样
终止条件:游标走到nums尽头再开始return
单层递归逻辑:每次递归都要储存该节点的path记录到result

待解决问题

如果把result.add(new ArrayList<>(path))放在for循环里实现呢?除了少了空集还有什么不同?

代码实现

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

    public void backtrack(int[] nums, int start){
        result.add(new ArrayList<>(path));//每次递归都要储存当前节点path的值

        if(start >= nums.length){ 
                return;
        }

        //注意:每个节点都是一个组合,而不是走到叶节点尽头、才生成path个组合
        for(int i=start;i<nums.length;i++){
            path.add(nums[i]); 
            backtrack(nums,i+1); 
            path.removeLast();
        }
    }
}

lc78 子集II

思路

其他同上。
78子集和40组合总和的变体
78:解决子集的return问题(每次递归都要return,而不是终止时return)
40:解决去重问题(排序成为有序数组,把当前值和前一位比较,若重复则跳过,不重复再进入循环)

代码实现

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

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums); 
        backtrack(nums,0);
        return result;
    }

    public void backtrack(int[] nums, int start ){
        result.add(new ArrayList<>(path));  

        if(start > nums.length){
            return;
        }

        for(int i=start;i<nums.length;i++){
            if(i> start && nums[i]==nums[i-1]){
                continue;
            }else{
                path.add(nums[i]);
                backtrack(nums,i+1);
                path.removeLast();
            }
        }
    }
}

lc46 全排列I

思路

其他同上。
引入一个开关used:标记不同的index(及其对应的元素)是否用过,用过则打开开关true

易错点

  • 回退与其他动作一荣俱荣一损俱损:如sum+,则回退后需要sum-;如标记used状态,则回退后需要打回unused

代码实现

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


    public List<List<Integer>> permute(int[] nums) {
        boolean[] used = new boolean[nums.length]; //注意这个used只能放在这里,因为nums是一个输入变量
        backtrack(nums,0,used);
        return result;
    }

    public void backtrack(int[] nums, int start,boolean[] used){
        if(start==nums.length){
            result.add(new ArrayList<>(path));
            return;
        }

        for(int i=0;i<nums.length;i++){
            if(! used[i]){
                path.add(nums[i]);
                used[i]=true;
                backtrack(nums,i+1,used);
                path.removeLast();
                used[i]=false; //注意!!如果回退,那标记的used也要还原为unused!
            }
        }
    }
}

lc47 全排列II

思路

与上文相比,增加了去重的需求。46和40的缝合。
46:引入used开关标记已经用过的元素
40:数组中元素重复问题:arrays.sort(nums),然后nums[i]==nums[i-1]看是否重复,重复则跳过

在递归时,跳过/不录入两种情况:
1. 已经used
2. 2. 元素和之前重复,若反复使用会使path重合

代码实现

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

    public List<List<Integer>> permuteUnique(int[] nums) {
        boolean[] used=new boolean[nums.length];
        Arrays.sort(nums);
        backtrack(nums,used);
        return result;
    }

    public void backtrack(int[]nums, boolean[] used){
        if(path.size()==nums.length){
            result.add(new ArrayList<>(path));
            return;
        }

        for(int i=0;i<nums.length;i++){
            //情况1:如果前一个元素没用过但是两个值相同则跳过
            if (i > 0 && nums[i] == nums[i - 1] && used[i - 1]==false) {
                continue;
            }
            //情况2:如果现在元素用过则跳过。可以合并写,更简单。
            if(used[i]){
                continue;
            }else{
                path.add(nums[i]);
                used[i]=true;
                backtrack(nums,used);
                path.removeLast();
                used[i]=false;
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值