首页 > 代码库 > Subsets II [leetcode] 从获取子集的递归和循环方法说起,解决重复子集的问题

Subsets II [leetcode] 从获取子集的递归和循环方法说起,解决重复子集的问题

这一题和Permutation II很像,一个是排列一个是组合。

我们理清思路,从最基本的获取子集的方法开始分析:

获取子集总的来说可以用循环或者递归做,同时还可以根据数字对应的binary code得到。

例如s = {x,y,z}可以生成的组合有:x,y,z,xy,yz,xz,xyz,0


第一种思路

1. 维护一个集合Set(i),包含s[0...i]可生成的所有组合

s[0...i+1]可以生成的所有组合为:Set(i) + (Set(i)+s[i+1])

void permutation(vector<int> &s, vector<vector<int>> & res, int index)
{
	permutation(s, res,  index + 1);
	for (int i = res.size(); i >= 0; i—)
	{
		res.push_back(res[i] + s[index]);
	}
}
以上是一个递归伪代码,我们可以将其改为循环的:

vector<vector<int>> permutation(vector<int> &s)
{
	vector<vector<int>> res;
	for (int index = 0; index < s.size(); index++)
	{
		for (int i = res.size(); i >= 0; i—)
		{
			res.push_back(res[i] + s[index]);
		}
	}
	return res;
}
2.下面考虑如何去除相同的子集

当输入为{x,y,y}时,对index = 0,res={0, x}; index = 1时res={0,x,y,xy}

当index=2时,问题来了。如果对0和x都加上y,会生成重复的子集y和xy。此时res={0,x,y,xy,y,xy,yy,xyy}

重复的原因是我们将index=1时处理过的集合{0,x}又处理了一遍。

所以我们加上preEnd标记上一个index处理过的集合的结尾位置(如{0,x})。

如果s[index]和s[index-1]一样,preEnd为处理s[index-1]之前res的大小;否则preEnd=0

代码如下:

vector<vector<int> > subsetsWithDup(vector<int> &s) {
        sort(s.begin(), s.end());
        vector<vector<int>> res;
        res.push_back(vector<int>());
        int preEnd = 0;
        for (int i = 0; i < s.size(); i++)
        {
            int resSize = res.size();
            for (int j = preEnd; j < resSize; j++)
            {
                vector<int> curSet = res[j];
                curSet.push_back(s[i]);
                res.push_back(curSet);
            }
            if (i + 1 < s.size() && s[i] == s[i + 1]) preEnd = resSize;
            else preEnd = 0;
        }
        return res;
    }

第二种思路

1.再回看所有的子集{x,y,z,xy,yz,xz,xyz,0}。我们发现x没有出现在第二位,y没有出现在第三位...

1.1设{x,y,z}为Set1,则对Set1中的每个元素,向其添加可行的其他元素,如x可以添加y和z;y可以添加z;z已经到末尾了,不能添加其他元素了。所以我们得到Set2{xy,xz,yz}。

1.2 接着向Set2添加元素,得到Set3{xyz}。Set3不可能再扩展出其他子集了,所以循环结束。

{0, Set1, Set2, Set3}是最终结果。

递归伪代码如下:

void permutation(vector<int> &s, int inPos, int outPos, vector<int> & out, vector<vector<int>> & res)
{
	for (int index = inPos; index < s.size(); index++)
	{
		out[outPos] = s[index];
		res.push_back(out);
		permutation(s, index + 1, outPos + 1, out, s);
	}
}
循环的时候,依次生成Set1,Set2,Set3。代码如下:

vector<vector<int> > subsets(vector<int> &s) {
        vector<vector<int>> res;
        vector<int> inPos;
        res.push_back(vector<int>());
        inPos.push_back(-1);
        sort(s.begin(), s.end());
        
        for (int i = 0; i < res.size(); i++)
        {
            for (int index = inPos[i] + 1; index < s.size(); index++)
            {
                vector<int> temp = res[i];
                temp.push_back(s[index]);
                res.push_back(temp);
                inPos.push_back(index);
            }
        }
        return res;
    }
下面考虑有重复元素的情况

在生成每个Seti的时候,均有可能遇见重复元素,从而生成重复子集,所以在里层的for循环里要跳过相同元素。

这里只需要加上简单的一句话即可 while(index + 1 < s.size() && s[index] == s[index + 1]) index++;

代码如下:

 vector<vector<int> > subsetsWithDup(vector<int> &s) {
        vector<vector<int>> res;
        vector<int> inPos;
        res.push_back(vector<int>());
        inPos.push_back(-1);
        sort(s.begin(), s.end());
        
        for (int i = 0; i < res.size(); i++)
        {
            for (int index = inPos[i] + 1; index < s.size(); index++)
            {
                vector<int> temp = res[i];
                temp.push_back(s[index]);
                res.push_back(temp);
                inPos.push_back(index);
                while(index + 1 < s.size() && s[index] == s[index + 1]) index++;
            }
        }
        return res;
    }


第三种思路

当前子集包含/不包含 index

递归来做的时候很简单,伪代码如下:

void permutation(vector<int> &s, int inPos, vector<int> & out, vector<vector<int>> & res)
{
	if (inPos == s.size()) 
	{
		res.push_back(out);
		return;
	}
	permutation(s, inPos + 1, out, res);
	permutation(s, inPos + 1, out + s[inPos], res);
}
非递归实现和第一种思路类似


第四种思路

含有K个元素的集合有2^K个子集,每个数字的第i位代表是否含有第s[i]个元素



Subsets II [leetcode] 从获取子集的递归和循环方法说起,解决重复子集的问题