分而治之:最大子数组问题
问题:
给出一个数组,找到一个子数组(连续的),使得该子数组的元素和是最大的。
输入:
给定一个数组X[1…n],对于任意一对数组下标为l,r(l≤r)的非空子数组,其和记为S(l,s).
输出:
求出S(l,s)的最最值,记为Smax .
问题分析
- 将数组X[1…n]分为X[1…n/2]和X[n/2 +1…n]
- 递归求解子问题
S1∶数组X[1…n/2]的最大子数组
S2∶数组X[n/2+1…n]的最大子数组 - 合并子问题,得到Smax
S3∶跨中点的最大子数组
数组X的最太子数组之和Smax =max{S1,sz,s3}
执行效率的瓶颈值在合并方面,也就是S3的求解问题。
S3的求解:
-
记mid = n/2
-
S3可分为左右两部分
Left:以X[mid]为结尾的最大子数组之和
Right:以X[mid+1]为开头的最大子数组之和
S3 = Left + RightLeft的求解:
从X[mid]向前遍历求和,并记录最大值
Right的求解:
从X[mid +1]向后遍历求和,并记录最大值
S3的时间复杂度:
- Left时间复杂度:O(mid)
- Right时间复杂度:O(n - mid)
- S3时间复杂度:O(n)
算法分析图
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N = 10000;
int q[N]; // 序列
// 求跨越中间的最大子序列
int findMidSum(int l, int mid, int r) {
int leftSum = -100000, rightSum = -100000; // 初始化为 -100000 是为了防止出现负数和的情况
int sum = 0; // sum 即向左和向右最大的子序列的和
for (int i=mid; i>=l; i--) {
sum += q[i];
// 从mid开始向左加不断更新最大值,最后计算出来的即mid左边最大的子序列的值
leftSum = max(leftSum, sum);
// max()函数为 #include<algorithm>中的方法,可以比较两个数的大小,返回较大的数
}
sum = 0;
for (int i=mid+1; i<=r; i++) {
sum += q[i];
// 从mid开始向右加不断更新最大值,最后计算出来的即mid右边最大的子序列的值
rightSum = max(rightSum, sum);
}
return leftSum + rightSum;
}
// 分治求左右两端最大的子序列和并将其与跨越中间的子序列的和比较大小,返回最大连续子序列的和
int maxSubArr(int l, int r) {
if (l == r) return q[l]; // 递归出口
int mid = l + r >> 1; //右移一位,相当于除2;也可以写成 mid = l+(r-l)/2;
int leftSum = maxSubArr(l, mid);
int rightSum = maxSubArr(mid+1, r);
int midMaxSum = findMidSum(l, mid, r);
int res = max(leftSum, rightSum);
res = max(res, midMaxSum);
// 返回最大的子序列和
return res;
}
int main() {
int n;
cin >> n;
for (int i=0; i<n; i++) scanf("%d", &q[i]);
int res = -100000;
res = maxSubArr(0, n-1);
cout << res;
return 0;
}