Leetcode 78&90. Subsets I & II 【排列与组合的生成总结】

78. Subsets

Given a set of distinct integers, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
For example,
If nums = [1,2,3], a solution is:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

题目

这是非常典型的深度优先搜索的题目,对于每一位,可以选择或者不选择,然后进行递归搜索,构建搜索树。注意:这一题的数字是没有重复的。

解法一

使用传统的dfs方法进行递归搜索构造。
代码如下:

class Solution {
public:
    vector<vector<int> > vvi;   
    vector<int> vi;
    void solve(vector<int> &s, int idx){
        if (idx == s.size()){                       
            vvi.push_back(vi);
            return;
        }
        solve(s, idx + 1);
        vi.push_back(s[idx]);
        solve(s, idx + 1);
        vi.pop_back();
    }
    vector<vector<int> > subsetsWithDup(vector<int> &S) {
        vvi.clear();
        vi.clear();
        solve(S, 0);
        return vvi;
    }
};

解法二

仔细考虑这个递归操作,其实就是每一位有两种可能,选或者不选,那不正是二进制的01吗?对于具有n个数字的数组,每一位有两个选择,总共就是(2^n)的可能,因此我们可以遍历从0~(2^n)-1的数字,每个数字的二进制表示即代表了一种选择,然后我们每次依次取一位,就可以构造出整个组合。这样避免了递归构建。
代码如下:

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int cnt = pow(2, nums.size());
        vector<vector<int> > vvi;
        for (int i = 0; i < cnt; ++i)
        {
            vector<int> vi;
            for (int j = 0; j < nums.size(); ++j)
            {
            // 选一位
                int tmp = 0x01 << j;
                if (tmp & i)
                {
                    vi.push_back(nums[j]);
                }
            }
            vvi.push_back(vi);
        }
        return vvi;
    }
};

90. Subsets II

题目

Given a collection of integers that might contain duplicates, nums, return all possible subsets (the power set).
Note: The solution set must not contain duplicate subsets.
For example,
If nums =[1,2,2], a solution is:

[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

题目解析

这一题与上面的区别就是会有重复的元素,如果直接套用上一题的办法,将会有重复答案,因为在生成的时候会认为两个不同位置的相同数字是不同的。怎么办呢,当然方法还是有的哟~

解法一

当然最简单的方法是,首先按照原来的方法生成组合,然后把结果放到set中进行去重复即可,注意:这里需要先排序,不然可能判重会出问题。当然这样的方法感觉很羞耻==
代码如下:

class Solution {
public:
    vector<vector<int> > vvi;
    set<vector<int> > svi;
    vector<int> vi;
    void solve(vector<int> &s, int idx){
        if (idx == s.size()){               
            svi.insert(vi);
            return;
        }
        solve(s, idx + 1);
        vi.push_back(s[idx]);
        solve(s, idx + 1);
        vi.pop_back();
    }
    vector<vector<int> > subsetsWithDup(vector<int> &S) {
        svi.clear();
        vvi.clear();
        // 排序
        sort(S.begin(), S.end());       
        solve(S, 0);
        for (set<vector<int> >::iterator it = svi.begin(); it != svi.end(); it++){
            vvi.push_back(*it);
        }
        return vvi;
    }
};

解法二

因为有重复,假设我们的输入序列是[1,2,2],我们的程序设计的目标就是先选第一个2和选第二个2不能区别,他们是等价的。因此想到了用一个map来存储每个元素的数量,我们只根据数量进行选择,而不根据元素的位置,因此将不同位置的2当成是一样的,没有任何区别。当搜素到2这个节点的时候,我们就有三种选择,选一个,选两个,或者一个都不选。
代码如下:

class Solution {
public:
    void solve(map<int, int> &mp, int idx, vector<vector<int>>&vvi, vector<int> &vi, vector<int>& nums)
    {
        if (idx == nums.size())
        {
            vvi.push_back(vi);
            return;
        }
        for (int i = 0; i <= mp[nums[idx]]; ++i)
        {
            for (int j = 0; j < i; ++j)
                vi.push_back(nums[idx]);
            solve(mp, idx+1, vvi, vi, nums);
            for (int j = 0; j < i; ++j)
                vi.pop_back();
        }    
    }

    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        map<int,int> mp;
        vector<int> numbers;
        for (const auto &i : nums)
            mp[i]++;
        for (auto it = mp.begin(); it != mp.end(); ++it)
            numbers.push_back(it->first);
        vector<vector<int> > vvi;
        vector<int> vi;
        solve(mp, 0, vvi, vi, numbers);
        return vvi;
    }
};

排列生成和组合生成分析

组合的生成是不管元素的顺序的,而排列的生成时相同的一组元素,不同的排列方式是当成不同的排列的。因此呢,在生成组合的时候,我们需要控制一个顺序,因为我们不需要这个顺序,他们只要元素一样,结果是一样的。但在排列生成的时候,我们需要在每一个搜索的结点上搜索所有的下一个节点,这样才能遍历全部的节点。排列和组合的生成本质上都可以看做有特定条件限制下的搜索树的生成。
以下给出对包含重复元素的序列的生成排列的代码,同样使用一个哈希来进行计数。如果是没有重复的序列,直接用一个布尔数组即可。
代码如下:

void solve(map<int, int> &mp, int idx, vector<vector<int>>&vvi, vector<int> &vi, vector<int>& nums,int size)
{
    if (idx == size)
    {
        vvi.push_back(vi);
        return;
    }
    for (int i = 0;i< nums.size(); ++i)
    {
        if (mp[nums[i]] > 0)
        {           
            vi.push_back(nums[i]);
            --mp[nums[i]];
            solve(mp, idx + 1, vvi, vi, nums, size);
            ++mp[nums[i]];
            vi.pop_back();
        }
    }
}

vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    map<int,int> mp;
    vector<int> numbers;
    for (const auto &i : nums)
        mp[i]++;
    for (auto it = mp.begin(); it != mp.end(); ++it)
        numbers.push_back(it->first);
    vector<vector<int> > vvi;
    vector<int> vi;
    int size = nums.size();
    solve(mp, 0, vvi, vi, numbers, size);
    return vvi;
}

欢迎指正。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值