快捷搜索: 王者荣耀 脱发

可重复选元素的组合问题(21)

1 问题

从n个元素中挑选m个元素,有多少组组合,可以重复选择。

输入:3 3

输出:10

解析:从三个元素中选取三个元素,可以重复选,共有多少组组合。111 112 113 122 123 133 222 223 233 333 一共十组组合,注:121 112 211是同一种组合,不能重复计算。

2 分析

如果采用递归,必然包含大量的重复计算,运算复杂度呈指数级别增长,肯定超时。

用二维数组进行分析,先枚举一定范围内的结果。arr[i][j]表示从i个元素中选取m个元素,所有组合数。

arr[i][1] = i,从i个元素中选取1个元素,可组成i个组合。

arr[1][j] = 1,从1个元素中选取j个元素,可组成一个组合。

arr[2][2] = 3,11 22 12 三种组合。

arr[4][2] = 10,从4个元素中选取2个元素,11 22 33 44 12 13 14 22 23 24 34 共有10种组合。

arr[2][4] = 4,从2个元素中选取4个元素,1111 2222 1122 1222 1112 共有4种组合。

。。。

1 2 3 4 1 1 1 1 1 2 2 3 4 5 3 3 6 10 15 4 4 10 20 35

观察上面二维表,不难发现规律,arr[i][j] = arr[i][j-1] + arr[i-1][j-1],就是当前元素的值等于左边的值加上上面的值,比如arr[4][4] = 25 = 15 + 20 = arr[4][3] + arr[3][3]。

所以此题建立一个二维数组,然后遍历整个二维数组,计算相应的值即可。

出于对空间复杂度的考虑,当每一行计算完成之后,只为下一次计算提供服务,除此之外再也没有用到,造成了大量的空间浪费,因此,可用一维滚动数组来解决。

如果算第i行第j列,就相当于第j列,arr[j] = arr[j-1] + arr[j],此时arr[j]被更新。以行i为外层循环,arr[1] = i,然后arr[j] = arr[j-1] + arr[j]。

3 代码

#include <iostream>
using namespace std;

void test() {
	int n, m;
	cin >> n >> m;    //从n中选取m个元素
					  //为了方便,角标从1开始,所以多申请一个空间
	int* arr = new int[m + 1];
	for (int i = 0; i <= m; i++)    //全部初始化为1
		arr[i] = 1;
	for (int i = 2; i <= n; i++) {
		arr[1] = i;              //第一个初始化为i
		for (int j = 2; j <= m; j++) {
			arr[j] = arr[j - 1] + arr[j];    //更新arr[j]		
		}
	}
	cout << arr[m] << endl;
}

int main() {
	test();
	system("pause");
	return 0;
}

4 打印所有组合

这个时间复杂度会很高,遍历所有可能,暂时没有想到更好的办法。

//这个主要参考了yysdsyl的博客

#include <vector>
#include <iostream>
#include <iterator>
using namespace std;

void mycom(int* arr, int n, int m, vector<int>& brr,int& sum) {
	//递归终止条件
	if (m == 1) {		//从n种元素中选1个元素,组合有n种
		sum += n;
		for (int i = 0; i < n; i++) {
			//copy(brr.begin(), brr.end(), ostream_iterator<int>(cout));
			for (int j = 0; j < brr.size(); j++)
				cout << brr[j];
			cout << arr[i];
			cout << endl;
		}
		return;
	}
	//递归终止条件
	if (n == 1) {		//从1种元素中选取m个元素,一共有一种
		sum++;
		for (int i = 0; i < brr.size(); i++)
			cout << brr[i];
		for (int i = 0; i < m; i++)
			cout << *arr;
		cout << endl;
		return;
	}
	brr.push_back(arr[0]);
	mycom(arr, n, m - 1, brr, sum);		//递归两种情况
	brr.pop_back();
	//规模减小
	mycom(arr+1, n - 1, m, brr, sum);		//递归两种情况
}

void test() {
	int n, m;
	cin >> n >> m;
	int sum = 0;
	vector<int> brr;
	int* arr = new int[n];
	for (int i = 0; i < n; i++)
		arr[i] = i + 1;
	mycom(arr, n, m, brr, sum);
	cout << endl;
	cout << sum << endl;
}

int main() {
	test();
	system("pause");
	return 0;
}
经验分享 程序员 微信小程序 职场和发展