主要由Introduction to algorithm中对于递归求解最大子数组方法中提出能否用线性时间寻找最大子数组的问题给出答案。
在原书中的Chapter 4中提到了 可以通过递归来求解最大子数组问题:
其主要思想是:将区间二分 这样最大的子数组只有三种情况,第一种是最大子数组完全位于二分后的左子数组中;第二种情况是最大子数组完全位于二分后的右子数组中;第三种情况是最大子数组跨过了二分的中点,既在左子数组中也在右子数组中。因此我们就可以得到这样的代码。
#include<iostream>
using namespace std;
const int N = 100010;
int A[N];
int n;
struct interval
{
int left;
int right;
int sum;
};
interval Find_Max_Crossing_Subarray(int low, int mid, int high)
{
int max_left = 0;
int left_sum = 1e-9;
int sum = 0;
for (int i = mid; i >= low; i--)
{
sum += A[i];
if (sum > left_sum)
{
left_sum = sum;
max_left = i;
}
}
int max_right = 0;
int right_sum = 1e-9;
sum = 0;
for (int i = mid + 1; i <= high; i++)
{
sum += A[i];
if (sum > right_sum)
{
right_sum = sum;
max_right = i;
}
}
return { max_left, max_right, left_sum + right_sum };
}
interval Find_Maximum_Subarray(int low,int high)
{
if (high == low)
return { low, high, A[low]};
else
{
interval left, right, middle;
int mid = low + high >> 1;
left = Find_Maximum_Subarray(low, mid);
right = Find_Maximum_Subarray(mid + 1, high);
middle = Find_Max_Crossing_Subarray(low, mid, high);
if (left.sum >= right.sum && left.sum >= middle.sum)
return left;
else if (right.sum >= left.sum && right.sum >= middle.sum)
return right;
else
return middle;
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> A[i];
interval maximum = Find_Maximum_Subarray(0, n - 1);
cout << maximum.left << " to " << maximum.right << " sum is " << maximum.sum;
}
在下面给出我思考了一部分的线性时间算法(思路解释在代码中):
#include<iostream>
using namespace std;
const int N = 100010;
int A[N];
int n;
struct interval {
int left;
int right;
int sum;
};
//要求在线性时间内 求最大子数组 即只通过遍历一遍数组 同时不断更新目前的最大子数组来求解
//当遍历到 i 时 最大子数组就是 i - 1 时的最大子数组 或者A[j...i]这个数组
//因此我需要做的是保持 当遍历到 i 时 找到当前最大子数组 以及A[j...i]这两个数组
//样例: 2 3 -7 2 -4 7
int main()
{
cin >> n;
for (int i = 0; i < n; i++)
cin >> A[i];
interval now = { 0, 0, A[0] }; //now 区间维护的是当前 最大的子数组
interval r = { 0, 0, A[0] }; //r 区间不断更新 寻找更大的子数组
for (int i = 1; i < n; i++)
{
if (r.sum < 0 && A[i] > r.sum) //当前一个A[j...i - 1]数组加起来都不如 A[i]大时 需要更新区间的左端点 从这边开始继续寻找最大数组
{
r.left = r.right = i;
r.sum = A[i];
}
else //当不需要更新左端点时 说明加上A[i] 后 A[j...i]更大 说明我们就可以延长右端点
{
r.right++;
r.sum += A[i];
}
if (now.sum < r.sum) //保持当前最大的子数组
{
now.left = r.left;
now.right = r.right;
now.sum = r.sum;
}
}
cout << now.left << " to " << now.right << " sum is " << now.sum;
}