目录
问题描述:
输入一个数值数组,确定具有最大和的连续子序列
vector<int> a;用来存储整个序列
maxSum记录最大连续子序列的和
left,right记录该序列的左右元素的下标;
1、穷举法,复杂度O(n^3)
将所有可能的连续子序列全部算一遍
int maxSum = 0, temp = 0, i = 0, j = 0, k = 0, left, right;
for (i = 0; i < a.size(); i++)
{//以i起
for (j = i; j < a.size(); j++)
{//到j止
temp = 0;
for (k = i; k <= j; k++)temp += a[k];//计算连续子序列[i,j]的和
if (temp > maxSum)
{
left = i;
right = j;
maxSum = temp;
}
}
}
2、穷举法改进,复杂度O(n^2)
用连续子序列[i,j]的和求连续子序列[i,j+1](若存在)的和
int maxSum = 0, temp, i, j, left, right;
for (i = 0; i < a.size(); i++)
{
temp = 0;
for j = i; j < a.size(); j++)
{
temp += a[j];
if (temp > maxSum)
{
left = i;
right = j;
maxSum = temp;
}
}
}
3、分治法,复杂度O(nlgn)
可知:最大子序列和的位置存在三种情况:1、完全在左半部分;2、完全在右半部分;3、跨越左右两部分。
所以分别求出左、右半部分的最大子序列和、以及跨越左右两部分的最大子序列和,三者中取最大即可
- 递归情况(recursive case):(子问题足够大且形式与原问题一样)
求mid_index=(left+right)/2;
求max1=递归left~mid_index
求max2=递归mid_index~right
求max3=左半部分最大和(包含其最后一个元素)+右半部分最大和(包含其第一个元素)
return max{情况1,情况2,情况3};
- 基本情况(base case):(子问题变得足够小,不再需要递归)
触底:子序列长度为1,返回该数
已知三种情况下最大连续子列,求整个区间内这三者的最大值。
int* maxSumRec(const vector<int>& a, int left, int right)
{//返回int[3]的数组,分别是sum,left_index,right_index
int* Max = new int[3];//为了使函数返回值不被销毁,必须new
Max[0] = a[left];
Max[1] = Max[2] = right;
if (left == right)return Max;//触底
int center = (left + right) / 2;
int *x, *y;
x = maxSumRec(a, left, center);
y = maxSumRec(a, center + 1, right);
if (x[0] >= y[0])
{
Max[0] = x[0];
Max[1] = x[1];
Max[2] = x[2];
}
else
{
Max[0] = y[0];
Max[1] = y[1];
Max[2] = y[2];
}
delete x, y;
//接下来求解第三种情况
int maxLeftSum = a[center], maxRightSum = a[center + 1], temp = 0;
int i, j, l, r;
//左半部分最大和(包含其最后一个元素)
for (i = j = center ; i >= left; i--)
{
temp += a[i];
if (maxLeftSum <= temp)//等于表示包含左侧0
{
maxLeftSum = temp;
j = i;
}
}
l = j;
//右半部分最大和(包含其第一个元素)
temp = 0;
for (i = j = center + 1; i <= right; i++)
{
temp += a[i];
if (maxRightSum <= temp) //等于表示包含右侧0
{
maxRightSum = temp;
j = i;
}
}
r = j;
temp = maxLeftSum + maxRightSum;
if (Max[0] < temp || (Max[0] == temp && ((Max[1] > l) || Max[2] < r)))//包含左零
{
Max[0] = temp;
Max[1] = l;
Max[2] = r;
}
return Max;
}
4、联机算法,复杂度O(n)
联机算法:在任意时刻对要操作的数据只读入(扫描)一次,一旦被读入并处理,它就不需要在被记忆了。而在此处理过程中算法能对它已经读入的数据立即给出相应子序列问题的正确答案。
容易想通:最大子序列的首位不可能是负数——因为负数只能拉低总和,不能增加总和,还不如舍弃
推广:任何负的子序列不可能是最优子序列的前缀
假设已知以位置i-1为结尾的最大子序列和b[i-1],对于以位置i为结尾的最大连续子序列和b[i]
综上b[i]=max( b[i-1]+a[i],a[i] )
然后只需要令b[0]=a[0],maxSum=a[0],然后从一开始按上述规则遍历求b[i],并用maxSum记录下最大的b[i]即可。
int* maxSubSum(const vector<int>& a)
{
int thisSum = a[0], left = 0;
int *maxSum = new int[3];
maxSum[0] = a[0];
maxSum[1] = maxSum[2] = 0;
for (int i = 1; i < a.size(); i++)
{
if (thisSum >= 0)thisSum += a[i];
else
{
thisSum = a[i];
left = i;
}
if (thisSum > maxSum[0])
{
maxSum[0] = thisSum;
maxSum[1] = left;
maxSum[2] = i;
}
}
return maxSum;
}