组合算法面试题

组合算法题往往有多个变种,如求一个集合的全部子集以及部分组合问题,这篇文章对这些常见的面试题做个汇总,权当做个记录,以免自己哪天忘了,难得到网路上去找。

一、求一个集合的全部子集

题目:给定一个集合s={a, b, c, d},试给出一个算法输出该集合的除了空集之外的全部子集。

分析:我们知道,一个集合的子集数目跟它的元素数目有关,集合元素数目为n,则子集数目为2^n。如包含两个元素的集合s1 = {a, b},则它的子集有:空集、{a}、{b}、{a, b}一共四个。求子集问题即从包含n个元素的集合中选取m个元素的问题,m可以是1...n。我们先从头扫描字符串的第一个字符。针对第一个字符,我们有两种选择:一是把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选取m-1个字符;二是不把这个字符放到组合中去,接下来我们需要在剩下的n-1个字符中选择m个字符。这两种选择都很容易用递归实现。实现代码如下:

[cpp]  view plain copy
  1. void Combination(char* string)  
  2. {  
  3.     if(string == NULL)  
  4.         return;  
  5.     int length = strlen(string);  
  6.     vector<char> result;  
  7.     for(int i = 1; i <= length; ++ i)  
  8.     {  
  9.         Combination(string, i, result); //从strng中选择i个字符,结果保存在result中  
  10.     }  
  11. }  
  12.   
  13.    
  14.   
  15. void Combination(char* string, int number, vector<char>& result)  
  16. {  
  17.     if(number == 0)  
  18.     {  
  19.         vector<char>::iterator iter = result.begin();  
  20.         for(; iter < result.end(); ++ iter)  
  21.             printf("%c", *iter);  
  22.         printf("\n");  
  23.         return;  
  24.     }  
  25.   
  26.     if(*string == '\0')  
  27.         return;  
  28.   
  29.     result.push_back(*string); //选择当前字符  
  30.     Combination(string + 1, number - 1, result);  
  31.     result.pop_back();  //不选当前字符  
  32.     Combination(string + 1, number, result);  
  33. }  

求子集还有一个更简单的算法,就是使用二进制。将数字从1——2^n-1循环,哪个位置位就选择。假定集合字符为{a, b ,c },则

001           a

010           b

011           a       b

...

依次类推就可。代码如下:

[cpp]  view plain copy
  1. void comb(char *str) {  
  2.     int len = strlen(str); //字符串长度,如str = “abc”长度为3  
  3.     int max = 1 << len; //字符串自己的数目,如"abc"子集数目为8  
  4.     for (int i=1; i<max; i++) {  //输出所有子集,这里除去空集,所以从i=1开始输出  
  5.         int k = i;  
  6.         int index = 0;  
  7.         while (k > 0) {  
  8.             if (k & 1) {   
  9.                 cout << str[index] << " ";  
  10.             }  
  11.             k >>= 1;  
  12.             index++;  
  13.         }  
  14.         cout << endl; //每次输出一个子集换行  
  15.     }  
  16. }  



二、人民币问题

题目:人民币有1元、2元、5元、10元、20元、50元、100元面值,试给出一个算法找出和为100的人民币组合(不包括100本身)。比如2张50的,或者2张50+2张20+1张10等。

分析:该题目与上面问题类似,可以采用组合的思路来解决。代码如下:

[cpp]  view plain copy
  1. /* 
  2.  * rmb.cpp 
  3.  * 
  4.  *  Created on: 2012-8-28 
  5.  *      Author: shusheng 
  6.  */  
  7.   
  8. #include <iostream>  
  9. using namespace std;  
  10.   
  11. #define N 6  
  12.   
  13. int w[N];  
  14. int number_used[N];  
  15. bool is_used[N];  
  16. int countnum = 0;  
  17.   
  18. void init()   
  19. {  
  20.     w[0] = 1;  
  21.     w[1] = 2;  
  22.     w[2] = 5;  
  23.     w[3] = 10;  
  24.     w[4] = 20;  
  25.     w[5] = 50;  
  26.     for (int i = 0; i < N; i++) {  
  27.         number_used[i] = 0;  
  28.     }  
  29. }  
  30.   
  31. void rmb(int start_index, int left_weight)   
  32. {  
  33.     if (left_weight == 0) {  
  34.         for (int i = 0; i < N; i++) {  
  35.             if (number_used[i] > 0)  
  36.                 cout << w[i] << "元: " << number_used[i] << "张 ";  
  37.         }  
  38.         cout << endl;  
  39.         countnum++;  
  40.         return;  
  41.     }  
  42.     for (int i = start_index; i < N; i++) {  
  43.         if (left_weight >= w[i]) {  
  44.             number_used[i]++;  
  45.             rmb(i, left_weight - w[i]);  
  46.             number_used[i]--;  
  47.         }  
  48.     }  
  49. }  
  50.   
  51. int main()   
  52. {  
  53.     init();  
  54.     rmb(0, 100);  
  55.     cout << countnum << endl;  
  56.     return 0;  
  57. }  
函数rmb(start_index, left_weight)的功能定义是从start_index开始选择,输出最终和为left_weight的所有钱币组合。这里类似于完全背包问题,即每一样钱币都可以选择多次,而背包容量大小为100,每样物品的价值就是钱币面值,只是这里不是求最大值,而是总的组合数目。

当然这里的代码可以修改成另外一种形式,也许更好理解,如下所示:

[cpp]  view plain copy
  1. void rmb(int start_index, int left_weight)  
  2. {  
  3.     if (left_weight == 0)  
  4.     {  
  5.         for (int i = 0; i < N; i++)  
  6.         {  
  7.             if (number_used[i] > 0) cout << w[i] << "元: "<< number_used[i] <<"张 ";  
  8.         }  
  9.         cout << endl;  
  10.         return;  
  11.     }  
  12.     for (int i = start_index; i < N; i++)  
  13.     {  
  14.         int y = left_weight;  
  15.         while (y >= w[i]) {  
  16.             number_used[i]++;  
  17.             y -= w[i];  
  18.             rmb(i+1, y);  
  19.         }  
  20.         number_used[i] = 0;  
  21.     }  
  22. }  
这里的代码for循环中就是先从1元开始选,这种情况完成后,再从5元开始选(即最小钱币值为5),再是10、20等。

三、整数分解问题

给定一个正整数,试输出所有的分解。如5=1+1+1+1+1 = 1+1+1+2=1+1+3=1+2+2=1+4

其实这个问题也可以参照上面的人民币的例子,只是这里的数组取值改成了1,、2、3、4...n-1。当然此题应该还有更好的解法,暂时以这个思路写一下:

[cpp]  view plain copy
  1. #include <iostream>  
  2. #include <vector>  
  3. using namespace std;  
  4. #define NUMBER 10  //要分解的数为10  
  5. static int cnt = 0; //分解数目  
  6. vector<int> part; //用于存储分解结果  
  7. void generate_partition(int x, int i, int v[])  
  8. {  
  9.     if (x == 0) {  //输出  
  10.         cout << ++cnt << ": ";  
  11.         for (int j=0; j<part.size(); j++) {  
  12.             cout << part[j] << " ";  
  13.         }  
  14.         cout << endl;  
  15.         return;  
  16.     }  
  17.   
  18.     for (int j=i; j<NUMBER-1; ++j) { //输出逻辑是先输出包含1个v[j]的,然后是2个v[j]的...  
  19.         int select = v[j];  
  20.         int c = 0, y=x;  
  21.         while (y >= select) {   
  22.             part.push_back(select);  
  23.             y -= select; c++;  
  24.             generate_partition(y, j+1, v);  
  25.         }  
  26.         while (c--)  
  27.             part.pop_back();  
  28.     }  
  29. }  
  30.   
  31. int main()  
  32. {  
  33.     int x = NUMBER;  
  34.     int v[NUMBER-1];  
  35.     for (int i=0; i<NUMBER-1; i++)  
  36.         v[i] = i+1;  
  37.     generate_partition(x, 0, v);  
  38.     return 1;  
  39. }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值