最大子数组和(递归)
问题描述
给定一个数组X[n] 求解该数组的最大子数组和。子数组 是数组中的一个连续部分。
例如:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
分解子问题
将一个完整的数组从中间mid = (low+high)/2一分为二,左侧[low,mid] 右侧[mid+1,high]
- S1作为左侧数组的最大子数组
- S2作为右侧数组的最大子数组
合并问题解
- S3作为跨中点的最大子数组
- 数组X的最大子数组和 Smax = max{S1,S2,S3}
如何求解
将数组递归分解后会达到以下的形式,我将以左下角红线圈定部分为例说明
- 此时low指向1,high指向-2,mid指向1。
左侧最大子数组和为S1 = 1,右侧最大子数组和S2 = -2,跨越中点的最大子数组和为1。求得最大子数组和为Smax = 1。 - 在上一步子数组的基础上增加了元素4。low指向1,high指向4,mid指向-2。
左侧最大子数组和为S1 = 1,右侧最大子数组和S2 = 4,跨越中点的最大子数组和为3。求得最大子数组和为Smax = 4。
这个Smax = 4能看出并不是简单的1+4 = 5,跨越中点的子数组必须包含mid以及mid+1指向的元素,这个跨越中点的最大子数组和为3是如何求解出来的,不在这里重复赘述,会在代码的注释中体现。
代码
#include <iostream>
#include <algorithm>
using namespace std;
//求跨越中间元素的最大值
//由于是求跨越中点的最大值,故必须有X[mid]与X[mid+1]这两个元素
//故以这两个元素为起点
int CrossingSubArray(int X[], int low, int mid, int high)
{
//求左侧包含X[mid]元素的最大值
int Sleft = INT_MIN;
int Sum = 0;
//以X[mid]为起点
for (int l = mid; l >= low; l--)
{
//比较加上一个元素的总和 Sleft 与不加上一个元素的总和 Sum 之间的大小
Sum = Sum + X[l];
Sleft = max(Sleft, Sum);
}
//求右侧包含X[mid+1]元素的最大值
int Sright = INT_MIN;
Sum = 0;
//以X[mid+1]为起点
for (int r = mid + 1; r <= high; r++)
{
//比较加下一个元素的总和 Sright 与不加下一个元素的总和 Sum 之间的大小
Sum = Sum + X[r];
Sright = max(Sright, Sum);
}
int S3 = Sleft + Sright;
return S3;
}
//求解最大子数组
int MaxSubArray(int X[], int low, int high)
{
int mid, S1, S2, S3, Smax = 0;
if (low == high)
{
return X[low];
}
else
{
mid = (low + high) / 2;
//分解原问题---->T(n/2)
S1 = MaxSubArray(X, low, mid);
//分解原问题---->T(n/2)
S2 = MaxSubArray(X, mid + 1, high);
//合并问题解---->T(n)
S3 = CrossingSubArray(X, low, mid, high);
Smax = max({ S1, S2, S3 });
return Smax;
}
}
int main()
{
int X[12] = { 1,-2,4,5,-2,8,3,-2,6,3,7,-1 };
int result = MaxSubArray(X, 0, 11);
cout << result;
return 0;
}
结果
-----------思想以及图片摘取自北航童咏昕教授算法公开课