力扣 90. 子集 II [先导题:47. 全排列 II和78. 子集]

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

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

示例 1:

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

示例 2:

输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10

题解

先导题目是:力扣 78. 子集 力扣 47. 全排列 II

力扣 78. 子集是此题的基础,基本结构没有变化,思路复制过来:

在递归函数中,每次传入当前下标idx,路径使用vector记录选择的元素,idx其对应的元素,有两种情况:选和不选,分别操作后继续进入递归函数:

  • 选:在当前路径cur中加入元素,再进入递归
  • 不选:cur退选元素,再进入递归

idx到达边界时,说明当前路径已经遍历完所有元素,记录当前路径cur,结束

而重复元素的解决办法来自力扣 47. 全排列 II,复制过来稍作修改:

因为可以重复,所以先来一个排序,方便后面处理重复,当面对下标idx时,如果nums[idx]与nums[idx-1]相等,应该加以判断是否选择idx

画图捋一下[1,1,2],取第二个11',[1,1',2],

面对1'时,因为1等于1':

  • 如果之前选择过1,说明之前肯定以[1,1']为开头去搜索其余的元素了,此时如果选择1'则会以[1',1]为开头去重复计算
  • 如果之前没有选择过1,现在选择1',不会造成重复.

所以前面说的加以判断是判定之前的1是否被使用过,如果没有用过才可以选择当前的1',如果用过就没有选择当前1',对应的判断语句是idx>0&&nums[idx]==nums[idx-1]&&!flag[idx-1])

注意这里有47题不一样的在于:如果判断得出选当前1'会造成重复,则不能直接return(在47题中是continue),而是直接进入递归函数,即当前路径cur不选择当前1'

查看代码
 class Solution {
public:
    vector<vector<int>> res;
    bool flag[10]={false};
    void dfs(vector<int>& cur,vector<int>& nums,int idx,int len){
        if(idx==len)//cur已经遍历完所有元素
        {
            res.emplace_back(cur);
            return;
        }
        //特殊情况:判断和去重
        //当前用过,就不选择当前元素,直接进入递归函数
        //如果和上一个相等且上一个没有选过,不选择当前元素,直接进入递归函数
        if(flag[idx]||(idx>0&&nums[idx]==nums[idx-1]&&!flag[idx-1])){
            dfs(cur,nums,idx+1,len);
            return;
        }
        //情况一:选择idx
        //标志位修改   
        flag[idx]=!flag[idx];
        //选择idx
        cur.emplace_back(nums[idx]);
        dfs(cur,nums,idx+1,len);
        //标志位复原
        flag[idx]=!flag[idx];
        //退选idx
        cur.pop_back();
        //情况二:不选择idx
        dfs(cur,nums,idx+1,len);
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<int> cur;
        sort(nums.begin(),nums.end());
        // work(cur,nums,0);
        dfs(cur,nums,0,nums.size());
        return res;
    }
};
posted @ 2023-03-28 15:47  付玬熙  阅读(10)  评论(0编辑  收藏  举报