回溯-子集

组合是无序的,满足方案要求即可,对应不同的题意,有时候元素可以重复,有时候不能重复。

排列是有序的,同一批元素可以有多种排列。

子集跟上面两种又不同,首先空集是子集,一个元素也是子集,数组本身也是子集的子集。

所以子集的求解更复杂。

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
我们要解决以下问题
1.对应子集,怎样是可行解?
答案是对应数组中的元素,有或者没有,都是一个可行解
2.什么情况下递归结束?
因为不确定子集中的元素,所以我们需要自己定义一个指针cur,每进入一次新的递归,cur向右移动一位,当cur到达数组末尾的时候就结束递归。
class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();

    public List<List<Integer>> subsets(int[] nums) {

        //定义一个指针cur,从前往后移动,cur==length,递归结束
        dfs(nums, 0);
        return res;

    }

    private void dfs(int[] nums, int cur) {
         res.add(new ArrayList<>(path));
        //递归到底
        if (cur == nums.length) {
            return;
        }
        //取当前位置的元素
        for(int i=cur;i<nums.length;i++){
            path.add(nums[i]);
            dfs(nums, i + 1);

        //不取当前位置的元素
            path.remove(path.size() - 1);
        }
        

    }
}

案例2:

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

本题目是案例1的进阶版,难点在于数组中有重复元素,子集中不能有重复子集。

这是一个典型的回溯算法,非常经典,因为它基本上用到了回溯需要的所有手段

一、回溯的跳出
大家知道,回溯就是递归版的穷举,这个不难,关键点在于递归何时退出?
我们知道这个题目是要所有子集,对于结果并没有元素的长度限制。
因此我们只能自己设置一个指针,每得到一个元素,指针右移一位,当指针指到最后一个元素时,本轮递归就到底了!

二、子集肯定是不能重复的
因为是子集,所以一个元素不能使用两次,不重复使用同一个元素,要求我们定义一个used数组,上层递归中使用过的元素,下层的递归不允许再用!

三、不能有重复的子集
前面两步估计大部分人都能搞定,难在第三点,数组中有重复元素,如果仅仅简单的穷举,生成的子集一定会重复。
但题目要求不能有重复子集。这个就要求我们在合适的地方进行剪枝,关键是在哪里减呢?
如果有一个元素跟它前面的元素相等,比如[1,2(1),2(2)],
当遍历到2(2)时,发现前面的2(1)还没用过,那么这个地方就该剪掉!
原因是什么?
本轮遍历的后面的2(2)了,前面的2(1)还没有用过,说明在上一轮循环中已经出现过一次[1,2(1)]了
所以这里如果不剪枝,肯定会跟前面的重复。

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

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        List<Integer> path = new ArrayList<>();
        int n = nums.length;
        boolean[] used = new boolean[n];
        dfs(nums, path, used, 0);
        return res;
    }

    private void dfs(int[] nums, List<Integer> path, boolean[] used, int cur) {
        res.add(new ArrayList<>(path));
        if (cur == nums.length) {
            return;
        }
        for (int i = cur; i < nums.length; i++) {
            if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
                continue;
            }
            if (!used[i]) {
                path.add(nums[i]);
                used[i] = true;
                dfs(nums, path, used, i + 1);
                path.remove(path.size() - 1);
                used[i] = false;
            }
        }

    }
}

 

posted @ 2022-11-01 19:35  Mars.wang  阅读(84)  评论(0编辑  收藏  举报