求解最大连续子数组问题
如下图所示为某公司开发的一款股票的走势图的软件,从图中我们可以看到,股票有涨有落,走势情况一目了然。现在要让你为软件添加一项新的功能:现在要用编程实现求解在某一时间段内,什么时候买入什么时候卖出获得的收益最大?
我们可以很容易的想到一种办法,就是逐个对每个时间点的组合进行计算比较,直到找出总收益最大的那一个组合,总共有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 ,这种递归式的算法对函数栈(线程栈)有一定的要求,在数据量特别庞大的情况下需要考虑栈的溢出问题。