欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

求解最大连续子数组问题

程序员文章站 2022-07-15 18:06:25
...

如下图所示为某公司开发的一款股票的走势图的软件,从图中我们可以看到,股票有涨有落,走势情况一目了然。现在要让你为软件添加一项新的功能:现在要用编程实现求解在某一时间段内,什么时候买入什么时候卖出获得的收益最大?

求解最大连续子数组问题求解最大连续子数组问题

我们可以很容易的想到一种办法,就是逐个对每个时间点的组合进行计算比较,直到找出总收益最大的那一个组合,总共有n*n中组合,时间复杂度为O(n^2)。

最终的收益是买入价格和卖出价格的差值,为了简化问题,我们可以将价格的变动转化为价格差的变动。作如下规定:

∆Pi = Pi - Pi-1

即,第i天的价格变化量等于第i天的价格减去第i-1天的价格。

那么,我们就将问题转化为:从每个时间点组合的价格变动总和中找出最大的。

如下所示的数组,记录了12天之内每一天价格变动的情况:

求解最大连续子数组问题求解最大连续子数组问题

那么现在的问题就转换为:寻找价格变动和最大那个组合。组合的数量为(n-1)*(n-1),时间复杂度仍旧为O(n^2)。

下面我们采用分治的思想进行求解。将当前的数组Arr[low...high]划分为两个相等规模的数组Arr[low...mid]和Arr[mid+1...high],那么我们要求解的最大连续子数组A[i...j]就存在于Arr[low...mid]、Arr[mid+1...high]以及Arr[low...high]中。也即是一下三种情况:

(1)Arr[low...mid]:low ≦ i ≦ j ≦ mid

(2)Arr[mid+1...high]:mid+1 ≦ i ≦ j ≦ high

(3)Arr[low...high]:low ≦ i ≦ j ≦ high

对于(1)和(2)两种情况,我们可以采用同样的算法,继续将其划分两个相等规模的数组,然后对三种情况进行处理。划分到什么时候停止呢?划分到数组中只有一个元素时,就无法进行划分,这时候就可以停止了。

这样一来,重复性就出现了,也就是说划分的算法是相同的,对于第(3)中情况,求解最大子数组的算法也是一样的:从中间向两边进行查找。下面用c代码实现:

/**
 * findMaxSubArray
 * @author liebert
* @param int arr[] 待查找的数组
* @param int low 数组的左边界
* @param int low 数组的右边界
* @param int result[] 结果数组 0 左边界,1 右边界,2 总和
* @return void
*/
void findMaxSubArray (int arr[], int low, int high, int result[])
{
	int i, j, sum, left,leftSum, right, rightSum, mid, crossSum;
	int resultLeft[3], resultRight[3], resultCross[3];

	if ( low == high ) {
		result[0] = low;
		result[1] = high;
		result[2] = arr[low];
} else {
	// 以中点作为分割点
	mid = (low + high) / 2;
	// 递归左边子数组
	findMaxSubArray (arr, low, mid, resultLeft);
	// 递归右边子数组
	findMaxSubArray (arr, mid+1, high, resultRight);
// 计算左边数组最大和 索引
	sum = 0; leftSum = 0;
	for (i = mid; i > low; i--) {
		sum += arr[i];
		if ( sum > leftSum ) {
			leftSum = sum;
			left = i;
		}
}
// 计算右边数组最大和 索引
	sum = 0; rightSum = 0;
for (j = mid+1; j < high; j++) {
	sum += arr[j];
		if ( sum > rightSum) {
			rightSum = sum;
			right = j;
		}
}
// 得到总和
crossSum = leftSum + rightSum;
	
	// 比较选择最大子数组
	if (resultLeft[2] > resultRight[2] && resultLeft[2] > crossSum) {
		result[0] = resultLeft[0];
		result[1] = resultLeft[1];
		result[2] = resultLeft[2];
} else if (resultRight[2] > resultLeft[2] && resultRight [2] > crossSum){
		result[0] = resultRight [0];
		result[1] = resultRight [1];
		result[2] = resultRight [2];
} else {
		result[0] = left;
		result[1] = right;
		result[2] = crossSum;
}
}

}

int main()
{
	int i;
	int arr[11] = {10,-5,-12,15,-2,10,-3,20,-2,-4,2};
	int res[3];
	findMaxSubArray(arr, 0, 10, res);
	
	printf("最大连续子数组为: ");
	for(i = res[0]; i <= res[1]; i++){
		printf("%d ", arr[i]);
	}
	printf("总和为:%d", res[2]);
}

输出结果:

最大连续子数组为: 15 -2 10 -3 20 总和为:40

算法的时间复杂度为:nlgn ,这种递归式的算法对函数栈(线程栈)有一定的要求,在数据量特别庞大的情况下需要考虑栈的溢出问题。