数对之差最大

问题:给定数组 a0,a1,a2...an ,求前一个元素减掉后一个元素的差组成的集合的最大值。

这个问题不是最大值最小值问题,因为不能确定最大值是不是在最小值前面。

设最终所求结果为 F。

1.先给出一种较易想到的方法。用动态规划方法。

设所求为 F ,现在假设与 ai 相关联的 F 是 Fi,能不能求出 Fi+1 呢?倘若可以有这样一个式子,便可在依次求 F 的过程中,保存最大的 F ,即是所求。深入思考,如果用 Fi 关联被减数(即 u - v 算式中的 u),那么,由于无法确定被减数是否已经被遍历过了(即,是否包含在 0 ~ i 个元素中),这不利于动态规划算式的展开。

动态规划实施过程中,应该让后一个待求量包含前面已求量的所有信息。

所以可以用 Fi 关联减数,这样可以保证被减数被遍历过,如此构建的动态规划状态转移方程有足够的信息。如图 1 给出一个状态转移图。


图1 由 i 到 (i + 1)状态转移

由上图知,Fi 转移到 Fi+1 ,设 u 是 0 ~ i - 1中的最大元素的下标,有两种可能,第一种是 Fi+1 = u 元素减去 i + 1 元素。第二种是一种特殊情况,i 元素比 u 元素大,Fi+1 = i 元素减去 i+1 元素。

这便是状态转移方程。如此,问题可解。


2.再给出分冶的解法。

如果现在给定了两个数组 A , B,为了求这两个数组拼接后的数组的 F,必须要知道哪些条件?显然,需要:

1).A 的 F 和 B 的 F,

2).A 中的最大值,B 中的最小值

实际上,1) 中的条件说明最终给出最大之差的两个元素都在 A 中,或者 B 中,通过对比 AF , BF 的大小确定哪一个是最终结果。而 2) 中的条件说明最终给出最大之差的两个元素,较大元素 max 在 A 中,较小元素 min 在 B 中,此时,max 必然是 A 中的最大值,min 必然是 B 中的最小值。

所以形成如下解法:

采用分冶的解法,将数组划分为左右两个数组,分别求出其 max,min ,F ,将这些值合并到合并数组的对应的值,则问题可解。


3.最后给出一种技巧性的解法。问题是求左边元素减右边元素的最大值,即求 :a0 - a1 , a1 - a2 ,a2 - a3,......这些值组成的集合 S 中,连续元素相加之和最大值。先证明这两个问题是等价的。

证明:如果 ai - aj 在考虑的范围内,由于 ai - aj  = (ai - ai+1) + (ai+1 - ai+2) + ... + (aj-1 - aj),所以 ai - aj 可以被转换为求 S 中连续的从 i 到 j-1 元素的和。

由于每一个差都能转换为 S 集合的多个连续的元素的和,因此,考虑原数组差集合中的最大值,等价于 S 集合中多个连续元素的和的最大值。

证毕!

之前有一篇文章就是描述的这个问题:http://blog.youkuaiyun.com/lizhihaoweiwei/article/details/37739421

至此,问题得到解决。


最后给出这三种方法的解法的原代码。

//动态规划解法
int solutionDP(int *data,int nLength)
{
	if(1 == nLength)
	{
		return *(data);
	}
	int max = *(data);
	int curDiff = *(data) - *(data + 1);
	int maxDiff = curDiff;
	for (int i = 2;i < nLength;++ i)
	{
		if(max < *(data + i - 1))
		{
			max = *(data + i - 1);
		}

		curDiff = max - *(data + i);

		if(maxDiff < curDiff)
		{
			maxDiff = curDiff;
		}
	}
	return maxDiff;
}

//分冶解法
#define INFINITESIMAL -999999999;
int solutionDiv(int *data,const int startPos,const int endPos,int *max,int *min)
{
	if(startPos == endPos)
	{
		*min = *max = *(data + startPos);
		return INFINITESIMAL;
	}
	int mid = (startPos + endPos) / 2;
	int leftMax,leftMin;
	int leftMaxDiff = solutionDiv(data,startPos,mid,&leftMax,&leftMin);
	int rightMax,rightMin;
	int rightMaxDiff = solutionDiv(data,mid + 1,endPos,&rightMax,&rightMin);

	*max = leftMax > rightMax ? leftMax : rightMax;
	*min = rightMin < leftMin ? rightMin : leftMin;
	int maxDiff = leftMaxDiff > rightMaxDiff ? leftMaxDiff : rightMaxDiff;
	if(leftMax - rightMin > maxDiff)
	{
		maxDiff = leftMax - rightMin;
	}
	return maxDiff;	
}

//转换为数组最大连续元素和解法
int solutionMaxSum(int *data,int nLength)
{
	if(1 == nLength)
	{
		return *(data);
	}
	if(2 == nLength)
	{
		return *(data) - *(data + 1);
	}
	int *subs = new int[nLength - 1];
	for (int i = 1;i < nLength;++ i)
	{
		*(subs + i - 1) = *(data + i - 1) - *(data + i);
	}

	int curDiff = 0,maxDiff = *(subs);
	
	for (int i = 0;i < nLength - 1;++ i)
	{
		maxDiff = curDiff > maxDiff ? curDiff : maxDiff;
		curDiff += *(subs + i);
		if(0 > curDiff)
		{
			curDiff = 0;
		}
		
	}
	return maxDiff;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值