您的位置:首页 > 其它

待字闺中之正数数组内和为指定数字的总数

2014-08-05 15:07 141 查看
求正数数组内和为指定数字的合并总数

比如[5, 5, 10, 2, 3] 合并值为 15 : 有4种 : (5 + 10, 5 + 10, 5 + 5 + 2 + 3, 10 + 2 + 3)

分析:有的时候,一个题目不能够立刻想到比较优化的想法,就可以先找到一个解决方案,然后根据方案的不足进行优化。而且这个时候,逆转一下思路,便会柳暗花明。由递归到动态规划,不就是如此么?

我们设定f(index,sum)表示数组从index开始到结束组成的sum的总数。那么,f(index, sum)可以表示为什么呢? 我们这个题目,就是要最终求得f(0, 15),从头开始分析,最终组成的和为15的可能组合中,可能包含第0个元素,也可能不包含, 原始数组为A:

当包含第0个元素时,剩下的表示为f(1, 15-A[0])
不包含第0个元素时,剩下的表示为f(1, 15)

则,f(0, 15) = f(1, 15) + f(1, 15 - A[0])。依次递归。递归的终止条件是什么呢?对于f(index,sum):

当和小于等于0的时候,f(index,sum) = 0
当和小于sum的时候, f(index, sum) = f(index + 1, num);
当和等于sum的时候,f(index, sum) = 1 + f(index + 1, sum);

但是,上面的条件,并没有使用题目中,数组全是正数,也就是存在负数也可以。如果仅仅是正数,后两个改为:

当和小于sum的时候, f(index, sum) = 0;
当和等于sum的时候,f(index, sum) = 1;

有一个条件,我们没有使用,也意味着提升的空间。

可是,上面的方案,时间复杂度是指数级。怎么做一些改进呢?一般在对一个算法进行优化的时候,有哪些思路呢?尤其是这种时间很恐怖的?我想很多同学都有这个经验,就是空间换时间。

大家可以想象动态规划的思想,大家看如下的状态转移方程:

dp
[m]=dp[n-1][m]+dp[n-1][m-num[n-1]]

dp
[m]表示前n个元素组成和为m的情况数。初始化dp[0][0]=1,其他为0。写出状态转移方程,大家也就明白了,为何要求全是正数了吧,直白一些,数组的索引,怎么可能为负呢?在计算的过程中,将和的情况保存下来,用空间换时间,整个算法的时间复杂度为O(n*m),不再是指数级。

具体代码如下:

int totalCountRecusive(vector<int>& data,int index,int sum)
{
	int length = data.size();
	if(sum == 0) return 1;//找到一个结果
	if(index == length)return 0;
	return totalCountRecusive(data,index+1,sum-data[index]) + totalCountRecusive(data,index+1,sum);//递归
}
int totalCountRecusive(vector<int>& data,int sum)//递归方法
{
	return totalCountRecusive(data,0,sum);
}
int totalCountDp(vector<int>& data,int sum)//动态规划方法
{
	int length = data.size();
	if(length <= 0)return 0;
	vector<vector<int> > dp(length+1);
	int i,j;
	for(i = 0;i <= length;i++)
	{
		vector<int> tmp(sum+1,0);//初始化
		dp[i] = tmp;
	}
	for(i = 0;i <= length;i++)dp[i][0] = 1;//初始化
	for(i = length-1;i >= 0;i--)
	{
		for(j = sum;j > 0;j --)
		{
			dp[i][j] = dp[i+1][j];
			if(j - data[i] >= 0)dp[i][j] += dp[i+1][j-data[i]];//分开,防止下标为负数
		}
	}
	return dp[0][sum];
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: