找出和为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

 

转载于:https://www.cnblogs.com/ZJUKasuosuo/archive/2012/08/16/2642338.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值