找出和为k的所有组合
1. 题目:给定一个数组a(a中不存在重复元素)和一个值k,求出a中元素之和为k的所有组合。比如a为{7,3,6,2},k=7,则所有组和有2+2+3和7。
2. 解析:回溯+递归。比较难以理解的是index索引的使用,代码如下:
void printSum(int candidates[], int index[], int n) { for (int i = 1; i <= n; i++) cout << candidates[index[i]] << ((i == n) ? "" : "+"); cout << endl; } //target:目标值 //sum:已经求得的和 //candidates:候选的数字 //sz:候选数字的个数 //index:记录被选中的数字的下标 //n:index有效记录的长度 //index和n是为了记录选中的候选数字信息,以便输出这些符合要求的候选数 void solve(int target, int sum, int candidates[], int sz, int index[], int n) { if (sum > target) return; if (sum == target) printSum(candidates, index, n); for (int i = index[n]; i < sz; i++) { index[n+1] = i; solve(target, sum + candidates[i], candidates, sz, index, n+1); } } void solve(int target, int candidates[], int sz) { int index[10000]; index[0] = 0; //0位置初始化为从candidates的下标0开始 solve(target, 0, candidates, sz, index, 0); }
index记录选中的元素的下标。需要注意的是for循环中的i=index[n],是为了保证每次选择新的元素都是从下标index[n]之后(包括index[n])开始选择,不会向前选择;index[n+1]=i,是说明下一个元素下标从i开始,i++保证index[n]之后的所有元素都会被选择。
3. 如果题目变成:a中每个元素只允许使用一次,该怎样修改代码?如输入10, 1, 2, 7, 6, 3, 5,k=8,输出为:1 2 5,1 7,2 6,3 5。只需将solve中的index[0]=0改成index[0]=-1,将index[n+1]=i改为index[n+1]=i+1。这样就保证下一次选择是从下一个元素开始的,同一个元素就会只被选择一次。
4. 如果a中允许重复元素出现,而输出结果中不允许出现重复的组合如:1 2 5和1 5 2是重复的组合,这个应该怎样修改?如果这样要求,就要将给定的数组排序,然后使用set存储最后的结果对,set中不存在重复的元素。代码如下:
1 //使用这种方式避免重复,前提是candidates必须是已经排好序的 2 //因为set<vevctor<int> >会认为(1,2,5)和(2,1,5)是不同 3 //的两个元素。如果已排序,那么就不会出现(2,1,5)这样的无序组合了 4 set<vector<int> > combinations; 5 void printSum(int candidates[], int index[], int n) { 6 vector<int> cm; 7 for (int i = 1; i <= n; i++) 8 cm.push_back(candidates[index[i]]); 9 combinations.insert(cm); 10 } 11 12 //target:目标值 13 //sum:已经求得的和 14 //candidates:候选的数字 15 //sz:候选数字的个数 16 //index:记录被选中的数字的下标 17 //n:index有效记录的长度 18 //index和n是为了记录选中的候选数字信息,以便输出这些符合要求的候选数 19 void solve(int target, int sum, int candidates[], int sz, int index[], int n) { 20 if (sum > target) 21 return; 22 if (sum == target) 23 printSum(candidates, index, n); 24 25 for (int i = index[n]+1; i < sz; i++) { 26 index[n+1] = i; 27 solve(target, sum + candidates[i], candidates, sz, index, n+1); 28 } 29 } 30 31 void solve(int target, int candidates[], int sz) { 32 sort(candidates,candidates+sz-1); 33 int index[10000]; 34 index[0] = -1; //0位置初始化为从candidates的下标0开始 35 solve(target, 0, candidates, sz, index, 0); 36 } 37 38 int main() 39 { 40 enum{aLength=7}; 41 int candidates[aLength]={1,1,2,5,6,7,10}; 42 int target=8; 43 solve(target,candidates,aLength); 44 set<vector<int> >::iterator iter=combinations.begin(); 45 while (iter!=combinations.end()) 46 { 47 for (int i=0;i<(*iter).size();i++) 48 { 49 cout<<(*iter)[i]<<" "; 50 } 51 cout<<endl; 52 iter++; 53 } 54 return 0; 55 }
5. 改动之后的题目很符合回溯算法的特点,一般在代码书写的时候使用vector,更清晰易懂。代码如下:
1 void printSum(vector<int> vec) { 2 for (int i = 0; i < vec.size(); i++) 3 cout << vec[i]<< " "; 4 cout << endl; 5 } 6 7 //target:目标值 8 //sum:已经求得的和 9 //candidates:候选的数字 10 //sz:候选数字的个数 11 //vec:vector存储已经选择的元素 12 //startId:从哪个下标对应的元素开始选择 13 //index和n是为了记录选中的候选数字信息,以便输出这些符合要求的候选数 14 void solve(int target, int sum, int candidates[], int sz, vector<int> vec, int startId) { 15 if (sum > target) 16 return; 17 if (sum == target) 18 { 19 printSum(vec); 20 } 21 22 for (int i = startId; i < sz; i++) { 23 vec.push_back(candidates[i]); //加入这个元素 24 solve(target, sum + candidates[i], candidates, sz, vec, i+1); 25 vec.pop_back(); //跳出这个元素 26 } 27 } 28 29 void solve(int target, int candidates[], int sz) { 30 vector<int> vec; 31 solve(target, 0, candidates, sz, vec, 0); 32 }
同样的,如果要去除重复的元素,仍然要对数组进行排序,然后使用set存储不同的元素。
6. 参考文章:
http://www.leetcode.com/2010/09/print-all-combinations-of-number-as-sum.html