最大子序乘积的问题分析
首先,题目概述:
这里的问题针对的是整形数组,即像A[10]={1,3-2,5,66,-45,63,0,2,1}这样的数组。
因此,可以知道数组的数可以分为三种类型:
1.正数
2.零
3.负数
除此之外,还有乘法的特性:正正得正,正负得负,负负得正,以及乘零等于零。
明白了上述基本数学内容后,现在给出两种分析思路,都可以O(n)的时间解决问题
方法1:
求取包含A[i-1]连续往左的最大和最小乘积,并与A[i]相乘,得到的结果和A[i]共同比较,从前面得到的三个数里再次挑选最大和最小值作为包含A[i]连续往左的最大和最小乘积。依次循环,同时每次循环都要比较包含A[i]连续往左的最大乘积和整个过程中的最大乘积,记录下来,最后返回。代码如下:
//以下代码是本人所写
//从左往右遍历整个数组,记录乘以A[i]后的最大imax和最小imin乘积
//imax和imin都是包含A[i]及向左连续的子序乘积
//之所记录最大和最小,是因为乘法的特性——负负得正
//A[i]之前的最小乘积可能因为A[i]>0,而使得最大乘积翻倍
//因此需要记录下最大和最小的乘积方便求取最大的乘积
int maxProduct(int A[], int n) {
if(n==1)
return A[0];
int imin=1,imax=1;
int resmax=A[0];
for(int i=0;i!=n;i++){
int tmax,tmin;
tmax=max(max(imin*A[i],imax*A[i]),A[i]);
tmin=min(min(imin*A[i],imax*A[i]),A[i]);
imax=tmax;
imin=tmin;
resmax=max(imax,resmax);
}
return resmax;
}
方法2:
注意两点:1.遇到零则重新计算;2.不包含零的情况下,连续最大乘积必然从最左或者最右端开始,即不可能存在于中间。
假设非零数组中存在处于中间的最大子序乘积A2,那么位于A2左边的乘积记录为A1,位于A2右边的乘积记录为A3。且A2最大不仅说明A1和A3都比A2小,即A2>A1,A2>A3,而且说明A2*A1<A2 , A2*A3<A2.否则A2是最大子序乘积的前提就不成立。那么
当A2>0时,A1<1和A3<1,即A1和A3都是负数,那么显然A1*A2*A3比A2,与假设相反
当A2<0时,A1>1和A3>1,即A1和A3都是正数,那么显然A1和A3都大于A2,与假设相反
因此,题设中的假设最大子序乘积位于数组中间是不成立的。也就能从上面的结论知道,最大子序乘积应该位于数组的两端,即包含最左或者最右的数。
代码如下:
//这段代码非本人所写,原作者可以在discuss里找到
//在看过此段代码后,立即明白了之前一直忽略的地方,总共有两处(我对原作者思路的复现):
//1.如果数组中有零,则零应该被特殊对待,因为任何数乘零都等于零,而最大乘积的趋向正整数
//2.对于非零数组段中的最大连续子段乘积只能出现在最左或者最右,即必须包含A[0]或则A[n-1]。具体证明将在csdn的博客中给出
//基于上述观点2,我们只需要从数组的首位两端同时连乘,并比较取最大,然后根据观点1,当连乘中遇到0的结果,则从下一个数重新开始连乘。
int maxProduct(int A[], int n) {
int b=1, f=1, res=A[0]; //b存储从左往右的连乘积,f存储从右往左的连乘积,res代表最大乘积
for(int i=0; i<n; i++){ //遍历整个数组
res=max(res, max(b*=A[i],f*=A[n-1-i])); //比较res,b和f取最大值赋给res
if(b==0) b=1; if(f==0) f=1; //A[i]或者A[n-1-i]为零,连乘从A[i+1]或者A[n-2-i]重新开始
}
return res;
}