回溯之子集

        在上一篇文章中,给出了组合问题的回溯解法,这次说明子集问题的回溯解法,先给出回溯的模板。

private void backtracking(参数1,参数2,...){
	if(递归终止条件){
		收集结果;
		return;
	}
	for(遍历集合){
		处理;
		backtracking(参数1,参数2,...); // 递归;
		回溯;
	}
}

子集

给你一个整数数组 nums,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。你可以按任意顺序返回解集。

示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        backtracking(nums, 0);
        return ans;
    }
    private void backtracking(int[] nums, int startIndex){
        ans.add(new ArrayList<>(path));
        for(int i = startIndex;i < nums.length;i++){
            path.add(nums[i]);
            backtracking(nums, i + 1);
            path.remove(path.size() - 1);
        }
    }
}

子集II

给你一个整数数组 nums,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。解集不能包含重复的子集。返回的解集中,子集可以按任意顺序排列。

示例 1:
输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    int[] used;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        used = new int[nums.length];
        backtracking(nums, 0);
        return res;
    }
    private void backtracking(int[] nums, int startIndex){
        res.add(new ArrayList<>(path));
        for(int i = startIndex;i < nums.length;i++){
            if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == 0) continue;
            path.add(nums[i]);
            used[i] = 1;
            backtracking(nums, i + 1);
            path.remove(path.size() - 1);
            used[i] = 0;
        }      
    }
}

        这题和上一题的问题的区别在于集合中的元素有重复,用题一中的解法就会出现重复的子集如示例给出的例子,用上一题解法得到[[],[1],[1,2],[1,2,2],[1,2],[2],[2,2],[2]],出现了两次[1, 2]。值得强调的是,由于子集中的元素无顺序要求,因此,[1, 2]和[2, 1]为同一个集合。总之,这一题需要进行去重操作,也是与上一题的唯一不同点,接下来将详细说明上述代码的执行过程。
        首先定义res集合用于收集所有符合条件的子集,path为其中某个子集。值得注意的是,子集问题不需要递归的终止条件,每次backtracking的开始直接对path中的子集进行收集。这一题与上一题的区别在于定义了一个used数组,表示下标i对应的元素是否被使用过,使用过用1表示,反之则为0。使用used数组进行去重操作,要求数组是有序的,因此在subsetsWithDup中对数组进行排序,同时初始化used数组,并调用backtracking,最终返回res集合。
        以[4,4,1,4]为例子,首先将数组进行排序得到[1,4,4,4],进入backtracking集时,path为空,此时将空集加入res集合中。之后进入for循环,遍历集合,startIndex从0开始,接着是if判断,改判断是去重的关键逻辑,此时i等于0,不满足if判断,接着将nums[i]加入path,path为[1],used[0] = 1。递归进入下一个backtracking,将集合[1]加入res集合中,进入for循环,此时startIndex从1开始,进入if判断,不满足nums[i] = nums[i - 1],将4加入path集合中,path为[1, 4],used[1] = 1,递归进入下一个backtracking,将集合[1, 4]加入res集合中,接着进入for循环,startIndex从2开始,进入if判断,此时nums[i] = nums[i - 1],但是nums[i - 1] = 1,不等于0,因此,将nums[2]加入path中,path为[1, 4, 4],used[2] = 1,递归进入下一个backtracking中。将[1, 4, 4]加入res集合中,进入for循环,此时startIndex从3开始,进入if判断,nums[i] == nums[i - 1],但是nums[i - 1] = 1,因此将nums[3]加入path中,path为[1, 4, 4, 4],used[3] = 1,递归进入下一个backtracking中,将[1, 4, 4, 4]加入到res集合中,进入for循环,此时startIndex从4开始,不小于nums的长度,不满足for循环条件,递归返回上一个backtracking,将nums[3]从path中移除,path为[1, 4, 4],将used[3]设置为0,i++,i等于4,不小于nums的长度,for循环结束,递归返回上一个backtracking,将nums[2]从path中移除,path为[1, 4],used[2]设置为0,i++,i等于3,进入if判断,nums[i] = nums[i - 1],并且used[2]为0,continue,i++,i等于4,不小于nums的长度,递归返回上一个backtracking,将nums[1]从path中移除,此时path为[1],used[1] = 0。i++,i等于2,nums[i] = nums[i - 1],并且nums[i - 1] = 0,continue,i++,i等于3,也是continue,i++,i等于4,不小于nums长度,for循环结束,递归返回到最初的backtracking,将nums[0]从path中移除,path为空,used[0]设置为0。如此走完了最初backtracking中for循环的第一次,按照上述流程,就能够遍历出所有符合条件的子集。
        在上述流程中,if判断阻止了通过for横向添加重复元素,但是允许通过backtracking纵向添加重复元素。

参考:带你学透回溯算法(理论篇)| 回溯法精讲!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值