问题:给定数组a[n],求其子数组的最大和。例如输入数组为:
1,-2,3,10,-4,7,2,-5
和最大的子数组为3,10,-4,7,2,因此输出为18.
没有想到更“巧妙”的算法前,用所谓“暴力”算法一般都能解决:
1、简单算法:
求出每个子数组的和,记录最大的那个即可。
void maxSum_1(int a[], int n)
{
int sum=NM,m=NM;//NM是一个很小的负数,例如-99999999.下同
for(int i=0; i<n; i++)
{
sum=0;
for(int j=i; j<n; j++)
{
sum += a[j];
m = max(m,sum);
}
}
cout<<__FUNCTION__<<" : "<<m<<endl;
}
算法复杂度是O(n2).书中的算法还有一个O(n3)的,比上面这个复杂之处是求sum时再从头开始,此处就不再提了。
2、分治法
把数组分成均等的两段l和u,那么原问题的解就是:最大子数组要么在l中,要么在u中,要么横跨l和u。
横跨中点时的情况比较难理解:这时左面不再是求最大子数组,而是从中间向两边求最大和。
int maxSum_2_helper(int a[], int l, int u)
{
if(l>u) return NM; //非法参数
if(l==u) return a[l]; //只有一个元素
int m = (l+u)/2 ;
///求左面一半的连续最大和
int lmax=NM;
for(int i=m,s=0; i>=l; i--)///从中点到最左端
{
s += a[i];
lmax = max(lmax,s);
}
///求右面一半的连续最大和
int rmax=NM;
for(int i=m+1,s=0; i<=u; i++)
{
s += a[i];
rmax = max(rmax,s);
}
///合并结果
///rlm是左右子数组中的最大子数组的和
int rlm = max(maxSum_2_helper(a,l,m), maxSum_2_helper(a,m+1,u)) ;
return max(rlm,lmax+rmax);///合并跨越中点的和两边的最大和
}
void maxSum_2(int a[], int n)
{
int m=maxSum_2_helper(a,0,n-1);
cout<<__FUNCTION__<<" : "<< m <<endl;
}
该算法非常微妙,其复杂度是O(nlogn).
3、巧妙的扫描算法
考虑如果我们已经求得a[0…i-1]的最大子数组和为m,那么a[0…i]的最大子数组要么在0~i-1中(存储在maxSoFar),要么截止到i(存储在maxEndingHere)。
void maxSum_3(int a[], int n)
{
int maxSoFar=NM, maxEndingHere=NM;
for(int i=0; i<n; i++)
{
maxEndingHere = max(maxEndingHere+a[i], a[i]);
maxSoFar = max(maxSoFar,maxEndingHere);
}
cout<<__FUNCTION__<<" : "<< maxSoFar <<endl;
}
这个算法的另一种形式:
void maxSum_3_2(int a[], int n)
{
int m=NM, sum=0;
for(int i=0; i<n; i++)
{
sum += a[i];
m = max(m,sum);
if(sum<0)
sum = 0;
}
cout<<__FUNCTION__<<" : "<< m <<endl;
}
相关问题:
a)查找总和最接近0的连续子数组,更一般的情况,查找最接近给定实数t的连续子数组。
如果a[i…j]最接近0,那么a[0…i]的和就与a[0…j]的和最接近。所以使用累加数组即可。由原数组构造新数组s[n](或者直接在原数组上操作),使s[i]=sum(a[0…i])。然后对s[n]排序,求出s[n]中最接近的两个数。这两个数的差值即为最接近0的子数组和。需要说明的是,这时需要新建数据结构,记录原来数组中每个元素的位置。
由于排序最快为O(nlogn),因此这个时间复杂度O(nlogn),空间O(n).
代码如下,使用stl的排序函数sort。
struct AP
{
int value;
int pos;
};
bool operator<(const AP & n, const AP &m)
{
return n.value<m.value;
}
template <typename T>
inline T abs(T n)
{
return n<0?(-n):n;
}
int nearZero(int a[], int n)
{
AP *b=new AP[n]();
int sum=0;
for(int i=0;i<n;i++)
{
b[i].pos = i;
sum += a[i];
b[i].value = sum;
}
sort(b,b+n);
///寻找差绝对值最小的两个元素
int dmin=NM;
int t = 0;
for(int i=1; i<n; i++)
{
if( abs(dmin) > abs(b[i].value - b[i-1].value))
{
dmin = abs(b[i].value - b[i-1].value);
t = i;
}
}
///t 和 t-1 位置元素之差极为最接近0
int r=NM;
int s,e;///s为子数组起始下表,e为子数组末尾下表
if(b[t].pos<b[t-1].pos)
{
r = b[t-1].value - b[t].value;
s = b[t].pos + 1;
e = b[t-1].pos;
}
else
{
r = b[t].value - b[t-1].value;
s = b[t-1].pos + 1;
e = b[t].pos;
}
cout<<__FUNCTION__<<" : "<< r <<endl;
cout<<"["<<s<<","<<e<<"]"<<endl;
delete [] b;
return r;
}
对于任意实数的问题,目前还没想到合适的解法。开始觉得对数组中每个元素平移是一个貌似可行的解决方法,但是仔细分析后发现是不行的。
后序还有最大子矩阵问题。
未完待续。
参考:http://www.cnblogs.com/wuyuegb2312/p/3139925.html