Combination, subset

本文深入探讨了多种组合问题的算法实现,包括子集生成、组合数计算、电话号码字母组合生成及组合求和等问题,提供了递归和非递归两种实现方式,并详细解释了每种方法的工作原理。

Subset

递归实现:

class Solution {
public:

void dfs(vector<int>& S,vector<int> path, int step, vector<vector<int> >& result)
{
    if(step == S.size())
    {
        result.push_back(path);
        return;
    }
    //不选S[step]
    dfs(S, path, step+1, result);
    //选S[step]
    path.push_back(S[step]);
    
    dfs(S, path, step+1, result);
    
    path.pop_back();
}
vector<vector<int> > subsets(vector<int> &S) 
{
    sort(S.begin(),S.end());
	vector<vector<int> > result;
	vector<int> path;
	dfs(S,path,0, result);
	return result;
}
};




二进制方法实现:

class Solution {
public:
    vector<vector<int> > subsets(vector<int> &S) 
{
	vector<vector<int> > result;
	if(S.size() == 0)        
		return result;

	sort(S.begin(), S.end());
	int n = S.size();
	vector<int> each;
    result.push_back(each);
	for(int i = 1; i < pow(2,n); ++ i)
	{
		each.clear();
		for(int j = 0; j < n ; ++ j )
		{
			if( (i >> j) & 1 == 1 )
				each.push_back(S[j]);

		}
		result.push_back(each);
	}
	return result;
}
};
与下一题相似的方法:

class Solution {
public:

void dfs(vector<int>& S,vector<int>::iterator start, vector<int> path,  vector<vector<int> >& result)
{
    result.push_back(path);
    for(auto i = start; i < S.end(); ++ i)
    {
        path.push_back(*i);
        dfs(S, i+1, path, result);
        path.pop_back();
    }
}
vector<vector<int> > subsets(vector<int> &S) 
{
    sort(S.begin(),S.end());
	vector<vector<int> > result;
	vector<int> path;
	dfs(S,S.begin(), path, result);
	return result;
}
};



SubsetII

我的方法,二进制的实现是投机取巧的方法,根本不能说是发现问题的本质,减小了运算量:
class Solution {
public:
    vector<vector<int> > subsetsWithDup(vector<int> &S) {
        vector<vector<int> > result;
	    if(S.size() == 0)        
	    	return result;

    	sort(S.begin(), S.end());
	    int n = S.size();
	    vector<int> each;
        result.push_back(each);
	    for(int i = 1; i < pow(2,n); ++ i)
	    {
	    	each.clear();
		    for(int j = 0; j < n ; ++ j )
		    {
			    if( (i >> j) & 1 == 1 )
				    each.push_back(S[j]);

		    }
		    vector<vector<int> >::iterator itr = find(result.begin(), result.end(),each);
		    if(itr == result.end())	
			    result.push_back(each);
	    
    	}
    	return result;
    }
};


参考别人的代码,递归求解,在combination sumII的问题中,是可能存在重复的元素,而且每个元素不能取值多余一次,但是不能有重复的组合输出,所以使用的是相邻的元素进行判断是否相同,相同跳过,在subsetII这个问题中,如果有相同的元素,为了避免输出一样的子串的值,必须保证所有可能的子串都输出,但是重复的子串不予输出,可是如果相同的元素出现在不同的dfs调用深度中,那么还是应该输出的毕竟是不同位置的元素,也是一种子串。和combination sumII意思相同。

class Solution {
public:

void dfs(vector<int>& S,vector<int>::iterator start, vector<int> path,  vector<vector<int> >& result)
{
    result.push_back(path);
    for(auto i = start; i < S.end(); ++ i)
    {
         if(i != start && *i == *(i-1))
             continue;
        path.push_back(*i);
        dfs(S, i+1, path, result);
        path.pop_back();
    }
}
vector<vector<int> > subsetsWithDup(vector<int> &S) 
{
    sort(S.begin(),S.end());
	vector<vector<int> > result;
	vector<int> path;
	dfs(S,S.begin(), path, result);
	return result;
}
};



Combination

没搞明白为什么必须是例如 k = 3, n = 5,必须初始化成11100然后不断求前一个排列,如果我设置成00111,求next排列就不对,prev也不对,但这样子做的计算复杂度是O((n-k)!), 空间复杂度是O(n)。
class Solution {
public:
    vector<vector<int> > combine(int n, int k) {
    vector<vector<int> > result;
	if(n <= 0)
		return result;
	vector<int> select(n,0);
	fill_n(select.begin(),k,1);
	
	do
	{
		vector<int> each;
		for(int i = 0; i < n; ++ i)
		{
			if(select[i] == 1)
				each.push_back(i+1);
			if(each.size() == k)
				break;
		}
		result.push_back(each);
	}while(prev_permutation(select.begin(),select.end()));
	return result;
    }
};

递归解法别人的代码,复杂度为O(n!),也比较好理解:
class Solution {
public:
   void dfs(int n, int k, int start, int cur, vector<int> &path, vector<vector<int> > &result)
   {
	  if(cur == k)
	  {
		result.push_back(path);
		return;
	  }

	  for(int i = start; i <= n; ++ i)
	  {
		path.push_back(i);
		dfs(n,k,i+1, cur+1, path, result);
		path.pop_back();
	  }
    }
    vector<vector<int> > combine(int n, int k) {
        vector<vector<int> > result;
        vector<int> path;
        dfs(n, k, 1, 0, path, result);
        return result;
    }
};

Letter Combinations of a Phone Number

 
这个就是电话号码对应的字母组合,编程之美,有,但是按照编程之美的方法,做出来的结果是不对的,因为他完全是没有考虑到有相同的号码怎么办,如果按照他的方法,用一个answer数组记录某个数字对应的字母的下标,在index == num.size()里面、for循环输出所有的字母组合,在出现重复数字的时候,输出来的字母也是相同的,例如 “22”,aa , bb , cc,而不是 aa bb cc ab ac bc,所以是不对的。数字只有0-9但是数字在号码中存在重复,数组用下标表示数字不足以存储他所有重复的对应的不同的字母下标。

别人的解法的妙处就是完全不去存储这样的信息,只是每次添加到path中,而且不是添加到真正的path,是调用下一次的副本加上了c,path本身没有添加任何元素
如果使用path += 。。。就会使得path真正添加了信息,这不是我们组合时候想要的,巧妙应用值传递,只是复制,但并没有改变原来的值,返回的时候还是回到上次调用的层次中,不包含多余信息。
class Solution {
public:
    char a[10][10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    int total[10] = {0, 0, 3, 3, 3, 3, 3, 4, 3, 4};
    void RecursiveSearch(vector<int> &num, string path, int index, vector<string>& result)
    {
	    if(index == num.size())
	    {
	    	result.push_back(path);
	    	return;//忘记了return
	    }

	    for(int i = 0; i < total[num[index]]; ++ i)
    	    {
    	    //path += a[num[index]][i];如何去实现添加每一个可能的字母,是组合,而不是再对同一个数字选择多于一个的字符,实现的很妙,我的方法就改变了path的值,会累加,但是每次值传递,返回的时候,path还是原来的值,没有添加刚刚输出的结果,这样子很妙
	    	RecursiveSearch(num,path + a[num[index]][i], index+1, result);
    	    }

    }


    vector<string> letterCombinations(string digits)
    {
    	vector<int> num;
    	for(size_t i = 0; i < digits.size(); ++ i)
    	{
	    	num.push_back(digits[i] - '0');
    	}
	string path = "";
        vector<string > result;
	RecursiveSearch(num, path, 0, result);
    	return result;
    }
};

别人的代码,比起我的简洁多了
class Solution {
public:
    vector<string> a = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    
    void RecursiveSearch(const string& digit, int index, string path, vector<string>& result)
    {
	    if(index == digit.size())
	    {
	    	result.push_back(path);
	    	return;//忘记了return
	    }

	    for(auto c: a[digit[index] - '0'])
    	    {
    	    //如何去实现添加每一个可能的字母,是组合,而不是再对同一个数字选择多于一个的字符,实现的很妙
	    	RecursiveSearch(digit, index+1, path + c,result);
    	    }

    }


    vector<string> letterCombinations(string digit)
    {
    	
        vector<string > result;
	    RecursiveSearch(digit, 0, "", result);
    	return result;
    }
};

Combination Sum

 

题意就是给定一些数,从这些数中可以找出一些组合,使得他们的和等于目标值,数可以重复取出
先贴我的错误代码,再分析别人的正确代码:

         for(int i = pre; i < num.size(); ++ i)
	{       <pre name="code" class="cpp"><span>		</span>each.push_back(num[i]);
		sum += num[i];
if(sum + num[i] > target) { each.pop_back(); return;} dfs(num, target, result, each, sum, i ); }

我错在每次递归后,没有在递归返回的时候出队列,还有从sum中减去出队列的数,还有就是当sum > target的时候,需要返回,但是不做操作,因为什么呢,因为sum和each都是值传递,返回到原来的调用的位置,这些值在递归中添加的值,就返回到原来的状态,只有原来层次中添加的值了,返回的时候要么是满足条件要么就是不满足回退,所以原来层次中添加的数据出队列,进入下一个i,保证从2,3,6,7依次每个数字开始遍历,而每个数据都是深度遍历,比如2,会进行2,2,2,2返回,2,2,3满足返回,2,2,6返回,2,2,7返回,2,3,3返回。。。。主要还是深度调用,其次重要的是用i的值保存队列中上一个位置,可以从上一个位置开始,但是不能是从0开始,因为允许重复取,但是必须有序,

class Solution {
public:
void dfs(vector<int> &num, int target, vector<vector<int> > &result, vector<int> each, int sum, int pre)
 {
	if(sum == target)
	{
		result.push_back(each);
		return;
	}

	for(int i = pre; i < num.size(); ++ i)
	{
		each.push_back(num[i]);
		sum += num[i];
		if(sum > target)
		     return;
		dfs(num, target, result, each, sum, i );
		each.pop_back();
		sum -= num[i];
	}
}


vector<vector<int> > combinationSum(vector<int> &num, int target)
{
	vector<vector<int> > result;
	sort(num.begin(), num.end());
	vector<int> each;
	
	dfs(num, target, result, each, 0, 0);
	return result;
}
};

在贴别人的简洁版代码:
直接不传递sum了,也不需要记得在sum+后进行sum-了,太酷了。
class Solution {
public:
void dfs(vector<int> &num, int gap, vector<vector<int> > &result, vector<int> each,  int pre)
 {
	if( gap== 0)
	{
		result.push_back(each);
		return;
	}

	for(int i = pre; i < num.size(); ++ i)
	{
		if(num[i] > gap)
			return;
		each.push_back(num[i]);
		dfs(num, gap - num[i], result, each,  i );
		each.pop_back();
	}
}


vector<vector<int> > combinationSum(vector<int> &num, int target)
{
	vector<vector<int> > result;
	sort(num.begin(), num.end());
	vector<int> each;
	
	dfs(num, target, result, each,  0);
	return result;
}
};


Combination Sum II

 

Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.

Each number in C may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • Elements in a combination (a1a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak).
  • The solution set must not contain duplicate combinations.
其实上一题也是需要去除重复的,用容器去重复,其实还是计算复杂度没有降低
class Solution {
public:
//每个数字只能被使用一次,并且组合的结合是没有重复元素的,
void dfs(vector<int> &num, int gap, vector<vector<int> > &result, vector<int> each,  int pre)
{
	if(gap == 0)
	{
		result.push_back(each);
		return;
	}

	for(int i = pre; i < num.size(); ++ i)
	{
		if(num[i] > gap)
			return;
		each.push_back(num[i]);
		dfs(num, gap - num[i], result, each,  i+1 );
		each.pop_back();
	}
}


vector<vector<int> > combinationSum2(vector<int> &num, int target)
{
	vector<vector<int> > result;
	sort(num.begin(), num.end());
	vector<int> each;
	dfs(num, target, result, each, 0);
	sort(result.begin(),result.end());
	result.erase(unique(result.begin(),result.end()),result.end());
	return result;
}
};

应该遇到相同的不处理才是正确的简洁的。

class Solution {
public:
//每个数字只能被使用一次,并且组合的结合是没有重复元素的,
void dfs(vector<int> &num, int gap, vector<vector<int> > &result, vector<int> each,  int pre)
{
	if(gap == 0)
	{
		result.push_back(each);
		return;
	}
	//因为我先进行了排序,所以排完以后相同的元素肯定相邻,有点像全排列如果有相同的元素怎么办
	//都是因为相同的元素会产生相同的排列,所以跳过相同的元素,不处理,前提是以某个元素开始的那个循环中,跳过跟自己相同的元素,而在深度递归中         //实际上,第一个循环是为了定位每一次dfs开始时候的位置,此时相同的不处理,可是在调用dfs的过程中,previous重新复制-1,i从pre开始,相同元素/        //出现在不同的dfs深度中还是可以选择的,例如,1 1 2 5 6 7 10 ,target = 8, 1 1 6就是一个解,1,2,5在第一个1的深度dfs中计算过,那么在遇到i=1        //,num[1] = 1的时候就不进行处理了。
        int previous = -1;
	for(int i = pre; i < num.size(); ++ i)
	{
	        if(previous == num[i])
	               continue;
		if(num[i] > gap)
			return;
		previous = num[i];
		each.push_back(num[i]);
		dfs(num, gap - num[i], result, each,  i+1 );
		each.pop_back();
	}
}


vector<vector<int> > combinationSum2(vector<int> &num, int target)
{
	vector<vector<int> > result;
	sort(num.begin(), num.end());
	vector<int> each;
	dfs(num, target, result, each, 0);
//	sort(result.begin(),result.end());
//	result.erase(unique(result.begin(),result.end()),result.end());
	return result;
}
};






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值