一、問題
給定一個長度爲N的整數數組,只允許使用乘法,不能用除法,計算任意(N-1)個數的組合乘積中最大的一組,並寫出算法的時間複雜度。
分析:
輸入:長度爲N的整數數組array。
輸出:(N-1)個數的組合最大乘積multiple。
約束:不能用除法計算。(如果可以使用除法,那麼先求得N個整數數組的乘積,再遍歷每個元素做除法運算即可求解,時間複雜度爲O(n))
二、解法
解法一 暴力求解法 -> 空間換時間
思路:最簡單的解法是遍歷每一種組和求解,共有N種組合,對於每個組合都需要做(N-1)次乘法運算,因此時間複雜度爲O(n^2)。
改進:考慮到大量的重複乘法工作,可以利用空間換時間,把重複的乘法工作利用最小的運算次數求得並把結果存儲到一片內存中,然後再利用這些結果計算求解,減少大量的重複計算工作。對於這道題,假設數組長度爲6,元素分別爲a1,a2,a3,a4,a5,a6,如下圖所示,要求(N-1)個數的所有組合的乘積,只需把下面每一行中左右兩個圓角框內的結果相乘即可得到(首行和末行較特殊,乘積即單個框結果)。圓角框可描述爲數組連續前i個數或後i個數的乘積。由於上下相鄰的圓角框內的結果是隻差一個乘積值,因此可用O(lgn)時間複雜度求得這些圓角框結果,利用圓角框的結果求最終解時也需O(lgn)時間複雜度。
時間複雜度:O(n)。
算法C實現:
上面圓角框結果採用兩個數組存儲,一個計算連續前n個數的乘積結果,頭元素爲1;一個計算連續後n個數的乘積結果,尾元素爲1。(求取時從頭至尾,從尾至頭掃描數組兩次)
/**
* @brief find the subarray the max multipulation using more memory space.
* the subarray with length n - 1 from input array with length n.
*
* @param[in,out] array array
* @param[in] count array length
*
* @return the max multipulation
*/
ElementType find_subarray_max_multipulation(ElementType* array,
CommonType count)
{
assert(array != NULL && count > 1);
CommonType i;
ElementType temp, max_mutipulation = INT_MIN;
/// memory space to save temp multipulation result
ElementType* head_multiple = SMALLOC(count, ElementType);
ElementType* tail_multiple = SMALLOC(count, ElementType);
/// get head multipulation result
head_multiple[0] = 1;
for (i = 1; i < count; i++)
head_multiple[i] = head_multiple[i - 1] * array[i - 1];
/// get tail multipulation result
tail_multiple[count - 1] = 1;
for (i = count - 2; i >= 0; i--)
tail_multiple[i] = tail_multiple[i + 1] * array[i + 1];
/// get max multipulation result
for (i = 0; i < count; i++) {
if ((temp = head_multiple[i] * tail_multiple[i]) > max_mutipulation)
max_mutipulation = temp;
}
return max_mutipulation;
}
解法二 數學分析/數學規律
思路:我們利用數學分析對這道題求解,考察N個數的乘積P的正負性快速判斷並剔除掉其中一個元素,從而留下的N-1個元素乘積即爲問題的解。
1.P = 0,那麼數組含0,假設去除1個0後的其他(N-1)個數乘積爲Q:
1.Q = 0:那麼整個數組含有至少2個非零數,解爲0;
2.Q > 0:解爲Q;
3.Q < 0:解爲0;
2.P > 0,那麼數組可能含有偶數個或0個負數,且數組不含0:
剔除掉最小的正數,剩下的N-1個數的乘積即爲問題的解;
3.P < 0,那麼數組含有奇數個負數,且數組不含0:
剔除掉最大的負數(即絕對值最小的負數),剩下的N-1個數的乘積即爲問題的解;
另外,判斷P的正負性我們不必要求解這個乘積再來獲得,可以統計數組大於0,等於0和小於0的元素個數來判斷,避免了乘法運算,這些數據也可以用於Q的正負性判斷。
(直接求N個數的乘積P在在編譯環境下往往會有溢出的危險(這也是本題不使用除法的潛在用意)(摘自《編程之美》))
綜上,求解步驟如下:
1.先遍歷一遍數組,統計數組中大於0、小於0以及等於0的元素個數,並找到最大的負數和最小的正數;
2.通過上面分析思路,找到剔除的元素,再遍歷一次數組求解最大乘積(實現時需注意可能存在多個最大負數或最小正數情況)。
時間複雜度:O(n)。
算法C實現:
/**
* @brief find the subarray the max multipulation using math method.
* the subarray with length n - 1 from input array with length n.
*
* @param[in,out] array array
* @param[in] count array length
*
* @return the max multipulation
*/
ElementType find_subarray_max_multipulation(ElementType* array,
CommonType count)
{
assert(array != NULL && count > 1);
CommonType i, flag_exclude = 0;
CommonType positive_count = 0, negative_count = 0, zero_count = 0;
ElementType max_mutipulation = 1;
ElementType min_positive = INT_MAX, max_negative = INT_MIN;
/// search needed math information from the array
for (i = 0; i < count; i++) {
/// count the positive, negative, zero value numbers
/// get the minimum positive value and maximum negative value
if (array[i] > 0) {
positive_count++;
if (min_positive > array[i])
min_positive = array[i];
} else if (array[i] < 0) {
negative_count++;
if (max_negative < array[i])
max_negative = array[i];
} else {
zero_count++;
}
}
/// get max multipulation by math analysis
if (zero_count > 0) {
if (zero_count > 1 || (negative_count & 0x01)) {
max_mutipulation = 0;
} else {
/// exclude 0 element
for (i = 0; i < count; i++) {
if (array[i])
max_mutipulation *= array[i];
}
}
} else if (negative_count & 0x01) {
/// exclude a maximum negative element
for (i = 0; i < count; i++) {
if (array[i] == max_negative && !flag_exclude) {
flag_exclude = 1;
continue;
}
max_mutipulation *= array[i];
}
} else {
/// exclude a minimum positive element
for (i = 0; i < count; i++) {
if (array[i] == min_positive && !flag_exclude) {
flag_exclude = 1;
continue;
}
max_mutipulation *= array[i];
}
}
return max_mutipulation;
}
三、總結
要善用元素的正負性判斷、是否爲零判斷求解一些最值問題。