【演算法拾遺】子陣列的最大乘積
給定一個長度為N的整數陣列,只允許使用乘法,不能使用除法,計算任意N-1個數的組合乘積的最大值。
這道題目重點要注意陣列中有負數、0的情況。最直觀的做法就是把所有可能的N-1個數的組合找出來,分別計算他們的乘積,並比較大小。找出所有組合需要O(N)時間,計算每個組合的乘積需要O(N)時間,因此該演算法的時間複雜度為O(N*N)。
程式設計之美上給出了兩種O(N)的解法。
第一種比較直觀,假設去掉第i個元素後的剩下的N-1個元素的成績為p[i],則從左向右掃描陣列,計算第0到第i-1個元素的乘積s[i],再從右向左掃描陣列,計算從第N-1個元素到第i+1個元素的乘積t[i],二者相乘便是除去第i個元素的剩下N-1個元素的乘積p[i],而後比較所有的p[i]即可。由於每次計算s[i+1]和t[i-1]時直接可以利用s[i]和t[i]的結果,因此掃描一次過去的時間複雜度為O(N),找出p[i]的最大值也是O(N),因此時間複雜度為O(N)。
第二種方法,將問題轉化到對去掉的那個元素的分析上,只在最後計算一次乘積即可。這種方法要先掃描一次陣列,得到陣列中正整數的個數、負整數的個數、0的個數,最小的正整數、絕對值最大的負整數和絕對值最小的負整數。而後詳細地根據陣列中正負數以及0的個數來判斷要剔除的元素。
1、如果陣列中0的個數大於1,則任意N-1個元素的乘積都為0,去掉任一元素均可;
2、如果陣列中0的個數為1,則需要分兩種情況;
{
1、如果陣列中負數的個數為偶數個,此時去掉0,剩下的N-1個數的乘積最大,為正值;
2、如果陣列中負數的個數為奇數個,此時N-1個數的乘積最大值為0,去掉任意一個非0元素即可。
}
3、如果陣列中沒有0,則需要分兩種情況:
{
1、如果陣列中的負數個數為奇數個,此時去掉一個負數後的剩下N-1個數的乘積為正值,要保證這個正值最大,我們需要去掉絕對值最小的負 數,即最大的負數;
2、如果陣列中的負數個數為偶數個,則需要分兩種情況:
{
1、如果陣列中沒有正整數,則去掉一個負數後,剩下的N-1個數的乘積為負值,要保證這個負值最大,我們需要去掉絕對值大的負數 , 即最小的負數;
2、如果陣列中有正整數,則去掉最小的正整數,剩下的N-1個元素的乘積即為最大的。
}
}
按照這種思路實現的程式碼如下:
bool flag; long long MaxProduct(int *arr,int len) { if(arr==NULL || len<1) { flag = false; return 0; } int minus = 0; //負數個數 int plus = 0; //正數個數 int zero = 0; //0的個數 int minAbsMinus = (signed int)0x80000000; //絕對值最小的負整數,先初始化為最小的int負數 int maxAbsMinus = -1; //絕對值最大的負整數,先初始化為最大的負整數 int minPlus = 0x7FFFFFFF; //最小的正整數,先初始化為最大的int正數 int i; for(i=0;i<len;i++) { if(arr[i] == 0) zero++; else if(arr[i] < 0) minus++; else plus++; if(arr[i]<0 && arr[i]>minAbsMinus) minAbsMinus = arr[i]; if(arr[i]<0 && arr[i]<maxAbsMinus) maxAbsMinus = arr[i]; if(arr[i]>0 && arr[i]<minPlus) minPlus = arr[i]; } int outNum; //不參與乘積的數 long long result = 1; //n-1個數的最大乘積 //0的個數大於1的情況,這時任意n-1個數的乘積都為0, if(zero > 1) return 0; //如果有一個0,則需要根據正負數的個數來決定 if(zero == 1) { //如果負數的個數為偶數個, //則去掉0後的n-1個數的乘積為正,即為最大值 if((minus&1) == 0) outNum = 0; //如果負數的個數為奇數個, //則去掉0後的n-1個數的乘積為負,因此最大值應該為0, //去掉任一個非0元素即可 else return 0; } //如果沒有0,則需要根據正負數的個數來決定 else { //如果負數個數為奇數個,則去掉一個負數後,剩下的n-1個元素的乘積為正, //此時去掉絕對值最小的負數,剩下的n-1個數的乘積便最大 if((minus&1) != 0) outNum = minAbsMinus; //如果負數個數為偶數個,這時候要分兩種情況, //陣列中有正數和沒正數 else { //如果陣列中沒有正數,則n-1個負數的乘積肯定為負數, //去掉絕對值最大的負數,便可得n-1個負數乘積的最大值 if(plus == 0) outNum = maxAbsMinus; //如果陣列中有正數,則去掉最小的正數,便可得n-1個數乘積最大值 else outNum = minPlus; } } //計算乘積 for(i=0;i<len;i++) { if(arr[i] != outNum) result *= arr[i]; } return result; }