组合算法面试题

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

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

题目:给定一个集合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个字符。这两种选择都很容易用递归实现。实现代码如下:

void Combination(char* string)
{
    if(string == NULL)
        return;
    int length = strlen(string);
    vector<char> result;
    for(int i = 1; i <= length; ++ i)
    {
        Combination(string, i, result); //从strng中选择i个字符,结果保存在result中
    }
}

 

void Combination(char* string, int number, vector<char>& result)
{
    if(number == 0)
    {
        vector<char>::iterator iter = result.begin();
        for(; iter < result.end(); ++ iter)
            printf("%c", *iter);
        printf("\n");
        return;
    }

    if(*string == '\0')
        return;

    result.push_back(*string); //选择当前字符
    Combination(string + 1, number - 1, result);
    result.pop_back();  //不选当前字符
    Combination(string + 1, number, result);
}

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

001           a

010           b

011           a       b

...

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

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



二、人民币问题

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

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

/*
 * rmb.cpp
 *
 *  Created on: 2012-8-28
 *      Author: shusheng
 */

#include <iostream>
using namespace std;

#define N 6

int w[N];
int number_used[N];
bool is_used[N];
int countnum = 0;

void init() 
{
	w[0] = 1;
	w[1] = 2;
	w[2] = 5;
	w[3] = 10;
	w[4] = 20;
	w[5] = 50;
	for (int i = 0; i < N; i++) {
		number_used[i] = 0;
	}
}

void rmb(int start_index, int left_weight) 
{
	if (left_weight == 0) {
		for (int i = 0; i < N; i++) {
			if (number_used[i] > 0)
				cout << w[i] << "元: " << number_used[i] << "张 ";
		}
		cout << endl;
		countnum++;
		return;
	}
	for (int i = start_index; i < N; i++) {
		if (left_weight >= w[i]) {
			number_used[i]++;
			rmb(i, left_weight - w[i]);
			number_used[i]--;
		}
	}
}

int main() 
{
	init();
	rmb(0, 100);
	cout << countnum << endl;
	return 0;
}

函数rmb(start_index, left_weight)的功能定义是从start_index开始选择,输出最终和为left_weight的所有钱币组合。这里类似于完全背包问题,即每一样钱币都可以选择多次,而背包容量大小为100,每样物品的价值就是钱币面值,只是这里不是求最大值,而是总的组合数目。

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

void rmb(int start_index, int left_weight)
{
    if (left_weight == 0)
    {
        for (int i = 0; i < N; i++)
        {
            if (number_used[i] > 0) cout << w[i] << "元: "<< number_used[i] <<"张 ";
        }
        cout << endl;
        return;
    }
    for (int i = start_index; i < N; i++)
    {
        int y = left_weight;
        while (y >= w[i]) {
            number_used[i]++;
            y -= w[i];
            rmb(i+1, y);
        }
        number_used[i] = 0;
    }
}
这里的代码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。当然此题应该还有更好的解法,暂时以这个思路写一下:

#include <iostream>
#include <vector>
using namespace std;
#define NUMBER 10  //要分解的数为10
static int cnt = 0; //分解数目
vector<int> part; //用于存储分解结果
void generate_partition(int x, int i, int v[])
{
    if (x == 0) {  //输出
        cout << ++cnt << ": ";
        for (int j=0; j<part.size(); j++) {
            cout << part[j] << " ";
        }
        cout << endl;
        return;
    }

    for (int j=i; j<NUMBER-1; ++j) { //输出逻辑是先输出包含1个v[j]的,然后是2个v[j]的...
        int select = v[j];
        int c = 0, y=x;
        while (y >= select) { 
            part.push_back(select);
            y -= select; c++;
            generate_partition(y, j+1, v);
        }
        while (c--)
            part.pop_back();
    }
}

int main()
{
    int x = NUMBER;
    int v[NUMBER-1];
    for (int i=0; i<NUMBER-1; i++)
        v[i] = i+1;
    generate_partition(x, 0, v);
    return 1;
}


参考资料

http://zhedahht.blog.163.com/blog/static/2541117420114172812217/

http://blog.csdn.net/yysdsyl/article/details/4215232



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值