字符的全排列与组合问题总结
简单来讲,排列和组合问题各分为两类,有重复和无重复。套上subsets模板后非常简单。
1.排列
1.1无重复字符
有两种思路,一种基于swap,另一种基于判断前面是否已经出现。
1.1.1基于swap的方法
1 class Solution { 2 public: 3 vector<vector<int> > permute(vector<int> &num) { 4 vector<vector<int> > result; 5 helper(num, 0, result); 6 return result; 7 } 8 9 void helper(vector<int> &num, int cur, vector<vector<int> > &result) 10 { 11 if(cur == num.size()) 12 { 13 result.push_back(num); 14 return; 15 } 16 17 for(int i = cur; i < num.size(); i++) 18 { 19 swap(num[cur], num[i]); 20 helper(num, cur+1, result); 21 swap(num[cur], num[i]); 22 } 23 } 24 };
1.1.2判断前面是否已经出现,未出现则加入
1 class Solution { 2 public: 3 vector<vector<int> > permute(vector<int> &num) { 4 vector<vector<int> > result; 5 vector<int> ivec; 6 helper(num, ivec, 0, result); 7 return result; 8 } 9 10 void helper(vector<int> &num, vector<int> &ivec, int cur, vector<vector<int> > &result) 11 { 12 if(cur == num.size()) 13 { 14 result.push_back(ivec); 15 return; 16 } 17 18 for(int i = 0; i < num.size(); i++) 19 { 20 int ok = 1; 21 22 for(int j = 0; j < cur; j++) 23 { 24 if(num[i] == ivec[j]) 25 { 26 ok = 0; 27 break; 28 } 29 } 30 if(ok) 31 { 32 ivec.push_back(num[i]); 33 helper(num, ivec, cur+1, result); 34 ivec.pop_back(); 35 } 36 37 } 38 } 39 40 41 };
1.2有重复字符
当有重复字符时,我们需要把每次进入递归后需要处理的字符当做是有序的,尤其是相同的字符,例如1,1,2可以看做是1(1), 1(2), 2(2),我们规定对于相同的字符,每次递归中只输出第一个(这里即为只对第一个进行交换)。因为如果两个都进行处理,会形成重复。代码如下:
1 class Solution { 2 public: 3 vector<vector<int> > permuteUnique(vector<int> &num) { 4 vector<vector<int> > res; 5 if(num.size() == 0) return res; 6 7 vector<int> ivec; 8 helper(num, 0, res); 9 return res; 10 } 11 12 void helper(vector<int> &num,int cur, vector<vector<int> > &res) 13 { 14 if(cur == num.size()) 15 { 16 res.push_back(num); 17 return; 18 } 19 20 for(int i = cur; i < num.size(); i++) 21 { 22 bool flag = true; 23 for(int j = cur; j < i; j++) 24 { 25 if(num[j] == num[i]){ 26 flag = false; 27 break; 28 } 29 } 30 if(!flag) continue; 31 32 swap(num[cur], num[i]); 33 helper(num, cur+1, res); 34 swap(num[cur], num[i]); 35 } 36 } 37 38 void swap(int& a, int& b) 39 { 40 int c = b; 41 b = a; 42 a = c; 43 } 44 };
2.Subsets
2.1无重复字符
1 class Solution { 2 public: 3 vector<vector<int> > subsets(vector<int> &S) { 4 vector<vector<int> > res; 5 int len = S.size(); 6 if(len == 0) return res; 7 8 vector<int> ivec; 9 10 sort(S.begin(), S.end()); 11 12 helper(S, 0, ivec, res); 13 return res; 14 } 15 16 void helper(vector<int> &S, int cur, vector<int> &ivec, vector<vector<int> > &res) 17 { 18 res.push_back(ivec); 19 20 for(int i = cur; i < S.size(); i++) 21 { 22 ivec.push_back(S[i]); 23 helper(S, i+1, ivec, res); 24 ivec.pop_back(); 25 } 26 } 27 };
2.2有重复字符
class Solution { public: vector<vector<int> > subsetsWithDup(vector<int> &S) { vector<vector<int> > res; int len = S.size(); if(len == 0) return res; vector<int> path; sort(S.begin(), S.end()); // don't forget this!!! helper(S, 0, path, res); return res; } void helper(vector<int> &S, int cur, vector<int> &path, vector<vector<int> > &res) { res.push_back(path); for(int i = cur; i < S.size(); i++) { if(i != 0 && i != cur && S[i] == S[i-1]) continue; path.push_back(S[i]); helper(S, i+1, path, res); // 注意这里应该是i+1,和排列里不同 path.pop_back(); } } };
3. Combination Sum
3.1
https://oj.leetcode.com/problems/combination-sum/
class Solution { public: vector<vector<int> > combinationSum(vector<int> &candidates, int target) { vector<vector<int> > res; int len = candidates.size(); if(len == 0) return res; sort(candidates.begin(), candidates.end()); vector<vector<int> > allbelow; for(int i = 0; i < len; i++) { vector<vector<int> > temp = allbelow; for(int j = 0; j < temp.size(); j++) { vector<int> ivec = temp[j]; int sum = 0; for(int m = 0; m < ivec.size(); m++) sum += ivec[m]; for(int m = 1; sum + candidates[i] * m <= target; m++) { ivec.push_back(candidates[i]); if(sum + candidates[i]*m == target) res.push_back(ivec); else allbelow.push_back(ivec); } } vector<int> newvec; for(int m = 1; m*candidates[i] <= target; m++) { newvec.push_back(candidates[i]); if(m*candidates[i] == target) res.push_back(newvec); else allbelow.push_back(newvec); } } return res; } };
3.2
https://oj.leetcode.com/problems/combination-sum-ii/
要求每个元素只能出现一次,典型的测试用例应该要能想到以下两个:
1)[1,1], target = 1 输出只能为一个[1]
2) [1,1], target = 2 输出为[1,1]
代码中特别应注意的就是i!=cur,表示当前的一次递归中相同元素仅输出第一次。
class Solution { public: vector<vector<int> > combinationSum2(vector<int> &num, int target) { int len = num.size(); vector<vector<int> > res; if(len == 0) return res; sort(num.begin(), num.end()); vector<int> subset; Combination(res, num, subset, 0, target); return res; } void Combination(vector<vector<int> >& res, vector<int> &num, vector<int> &subset, int cur, int target) { int sum = 0; for(int i = 0; i < subset.size(); i++) sum += subset[i]; if(sum == target) { res.push_back(subset); return; } if(sum > target) return; for(int i = cur; i < num.size(); i++) { if(i != cur && num[i] == num[i-1]) continue; // NOTE : i != cur is important. only show once in one recursion subset.push_back(num[i]); Combination(res, num, subset, i+1, target); subset.pop_back(); } } };
_______下面是去年的总结,现在在来看居然看不懂,写的太复杂了 吧_______
给定字符的全排列
- 没有重复字符的情况
- 题解与分析见http://zhedahht.blog.163.com/blog/static/254111742007499363479/,但该解法只适用于没有重复字符串的情况,如果有重复字符串按照该方法就可能产生重复的排列,因此需要进行改进。
- 代码如下:
-
void Permutation(char* pStr, char* pBegin) { if(!pStr || !pBegin) return; // if pBegin points to the end of string, // this round of permutation is finished, // print the permuted string if(*pBegin == '\0') { printf("%s\n", pStr); } // otherwise, permute string else { for(char* pCh = pBegin; *pCh != '\0'; ++ pCh) { // swap pCh and pBegin char temp = *pCh; *pCh = *pBegin; *pBegin = temp; Permutation(pStr, pBegin + 1); // restore pCh and pBegin temp = *pCh; *pCh = *pBegin; *pBegin = temp; } } }
-
-
上面的方法是基于交换的,使用模板实现如下:
-
1 #include <iostream> 2 using namespace std; 3 4 template <typename T> 5 void Perm(T a[], int k, int m) 6 { 7 if(k==m) 8 { 9 for(int i=0; i<=m; i++) 10 cout<<a[i]; 11 cout<<endl; 12 } 13 else 14 { 15 for(int i=k; i<=m; i++) 16 { 17 swap(a[k], a[i]); 18 Perm(a, k+1, m); 19 swap(a[k], a[i]); 20 } 21 } 22 } 23 24 void main() 25 { 26 int a[]={1,2,3,4,5}; 27 char b[]="abcde"; 28 Perm(b, 0, 4); 29 }
-
- 有重复字符出现
- 如果给定的字符中有重复字符出现,那么去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换,如果要避免产生重复的排列只需要在for循环后加入一个判定条件,代码如下:
1 int status = 1; 2 for ( char* k = pBegin; k<pCh ; ++k) 3 { 4 if (*k==*pCh && pCh!=pBegin) 5 { 6 status = 0; 7 break; 8 } 9 } 10 if (status == 0) 11 { 12 continue; 13 }
- 如果给定的字符中有重复字符出现,那么去重的全排列就是从第一个数字起每个数分别与它后面非重复出现的数字交换,如果要避免产生重复的排列只需要在for循环后加入一个判定条件,代码如下:
- 题目:输入一个字符串,输出该字符串中字符的所有组合。举个例子,如果输入abc,它的组合有a、b、c、ab、ac、bc、abc。
- 没有重复字符的情况
- 解法与分析见何海涛博客,但是该解法同样只能处理不包含重复元素的情况,对于含有重复元素的情况无能为力。下面是我自己的实现,不是很标准,将就看一下:)
1 #include<stdio.h> 2 3 void combination(char* pStr); 4 void combination(char* pStr, char* pBegin, int len, int index, int sum); 5 void printStr(char * pStr, char *pLast, int sum); 6 7 int _tmain(int argc, _TCHAR* argv[]) 8 { 9 char pStr[] = "abc"; 10 combination(pStr); 11 getchar(); 12 return 0; 13 } 14 15 void combination(char* pStr) 16 { 17 if(pStr == NULL) return; 18 19 int count = 0; 20 for(char* pBegin = pStr; *pBegin != '\0'; pBegin++) 21 { 22 count++; 23 } 24 for(int i = 1; i <= count; i++) 25 { 26 combination(pStr, pStr, i, 0, 0); 27 } 28 } 29 30 31 void combination(char* pStr, char* pBegin, int len, int index, int sum) 32 // len is the length of remaining string 33 // index is the pBegin location(start from 0) 34 // sum is the current selected chars summation marker (1,2,4,8...)summation of them 35 { 36 if(len == 0) 37 { 38 printStr(pStr, pBegin, sum);// How to mark the char 39 return; 40 } 41 if(*pBegin == '\0') return; 42 int flag = (1 << index); 43 44 45 combination(pStr, pBegin+1, len-1, index+1, sum+flag); 46 //*pBegin = -1;// This is not right, I should not change the original string 47 combination(pStr, pBegin+1, len, index+1, sum); 48 } 49 50 51 void printStr(char * pStr, char *pLast, int sum) 52 { 53 int i = 1; 54 for(char* pBegin = pStr; *pBegin != *pLast; pBegin++, i<<=1) 55 { 56 //if(*pBegin != -1) 57 //printf("%c", *pBegin); 58 if((sum & i) != 0) 59 printf("%c", *pBegin); 60 } 61 printf("\n"); 62 }
- 解法与分析见何海涛博客,但是该解法同样只能处理不包含重复元素的情况,对于含有重复元素的情况无能为力。下面是我自己的实现,不是很标准,将就看一下:)
- 给定字符中包含重复字符的情况
- http://blog.csdn.net/cxllyg/article/details/7621207这里给除了一种方法,把所有的字符串组成链表,生成新的字符串时逐个与链表中的字符串比较如果没有出现时,则加入链表中,如果出现过则,不加入链表中,最后输出整个链表。
1 #include <iostream> 2 #include <string.h> 3 using namespace std; 4 5 #include <iostream> 6 using namespace std; 7 8 typedef struct LNode{ 9 char data[10]; 10 LNode* next; 11 }*List; 12 13 void InsertList(List &l, char data[]) 14 { 15 LNode *p=new LNode; 16 strcpy(p->data,data); 17 if(NULL==l) 18 p->next=NULL; 19 else 20 p->next=l; 21 l=p; 22 } 23 24 void Print(List l) 25 { 26 LNode *p=l; 27 while(p) 28 { 29 cout<<p->data<<endl; 30 p=p->next; 31 } 32 } 33 34 bool isContain(List l, char data[]) 35 { 36 LNode *p=l; 37 while(p && strcmp(p->data,data)!=0 ) 38 p=p->next; 39 40 if(!p) 41 return false; 42 else 43 return true; 44 45 46 } 47 48 List l=NULL; 49 50 void Backtrack(char str[], char out[], int length, int curr, int start)//全组合 51 { 52 for(int i=start; i<length; i++) 53 { 54 out[curr]=str[i]; 55 out[curr+1]='\0'; 56 // cout<<out<<endl; 57 if(!isContain(l, out))//判断是否包含此种结果,不包含则插入链表 58 InsertList(l, out); 59 60 61 if(i<length-1) 62 Backtrack(str, out, length, curr+1, i+1); 63 } 64 } 65 66 void main() 67 { 68 char str[]="1223"; 69 char *out=new char[strlen(str)+1]; 70 Backtrack(str, out, strlen(str), 0, 0); 71 72 Print(l); 73 74 75 }
输出结果:
- 下面给出另一种方式,对字符串处理时如果某个字符在处理时,使用的方程基于Cnk=Cn-1k+Cnk-1,而且不存在交换,如果某个字符的前面有相同字符未被选中,则这个也不能被选中。例如abba,含有2个字符时输出为ab,aa,bb当前面的a已经在某次迭代中被选中时后面的a如果不是组成aa的情况,就不应该在此被选中。
1 #include <iostream> 2 #include <vector> 3 #include <string> 4 using namespace std; 5 static int result_count; 6 vector<int> count(string str) 7 { 8 vector<int> ch_count_gol(129),ch_count_local; 9 10 int size = str.size(); 11 int index=0; 12 while (index<size) 13 { 14 ch_count_local.push_back(ch_count_gol[static_cast<int>(str[index])]); 15 ++ch_count_gol[static_cast<int>(str[index])]; 16 index++; 17 } 18 return ch_count_local; 19 } 20 21 22 void print(string str,int size,int index, vector<int> ch_count_local,string out,vector<int> record,int length) 23 { 24 if (length == 0) 25 { 26 result_count++; 27 cout<<result_count<<" : "<<out<<endl; 28 return ; 29 } 30 if (index == size) 31 { 32 return ; 33 } 34 35 36 if (record[static_cast<int>(str[index])]<ch_count_local[index]) 37 { 38 print(str,size,index+1,ch_count_local,out,record,length); 39 } 40 else 41 { 42 print(str,size,index+1,ch_count_local,out,record,length); 43 44 45 out+=str[index]; 46 47 48 ++record[static_cast<int>(str[index])]; 49 print(str,size,index+1,ch_count_local,out,record,length-1); 50 } 51 } 52 53 54 void print(string str) 55 { 56 vector<int> ch_count_local; 57 ch_count_local = count(str); 58 59 string out; 60 vector<int> record(128); 61 for (int length = 1;length<=str.size();length++) 62 { 63 print(str,str.size(),0,ch_count_local,out,record,length); 64 } 65 } 66 67 68 int main() 69 { 70 string str("bbc"); 71 print(str); 72 cout<<result_count; 73 }
- http://blog.csdn.net/cxllyg/article/details/7621207这里给除了一种方法,把所有的字符串组成链表,生成新的字符串时逐个与链表中的字符串比较如果没有出现时,则加入链表中,如果出现过则,不加入链表中,最后输出整个链表。
参考:http://blog.csdn.net/lyy838543644/article/details/7642130
你问我生命中还有什么可追寻?