求解最大连续子数组问题

来源:互联网 发布:淘宝积分在哪里查看 编辑:程序博客网 时间:2024/06/11 21:49

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


我们可以很容易的想到一种办法,就是逐个对每个时间点的组合进行计算比较,直到找出总收益最大的那一个组合,总共有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 ,这种递归式的算法对函数栈(线程栈)有一定的要求,在数据量特别庞大的情况下需要考虑栈的溢出问题。