本次的兩個算法題是子數組的積的問題,勉強也能算做子數組和的問題。
問題一:乘積小於k的子數組
問題描述:
給定一正整數數組以及一整數k,要求找到所有乘積小於k的子數組的個數。
input : nums = [3 5 2 8] k = 17
output: 7 解釋: [3] [5] [2] [8] [3 5] [5 2] [2 8]
解法一:前綴和 + 二分搜索
看到題的第一反應還是用前綴和數組求解。但是由於是乘積可能會出現越界情況,因此對num求對數。此時的乘法就變爲了加法。因此依然可以使用前綴和的結構,遍歷出所有的子數組,以O(N^2)的時間複雜度求解。不過由於數組中全是正整數,因此前綴和數組的單調遞增的,因此可以使用二分查找優化其時間複雜度降爲O(Nlog(N))。
大體思路還是這樣,找到依次以當前結點開頭的乘積小於k的最長子數組,此時該最長子數組中的以當前結點開頭該子數組的所有子數組都滿足條件。實現代碼如下:
// 方法一 前綴和 + 二分查找
public int numSubarrayProductLessThanK(int[] nums, int k) {
double [] sums = new double[nums.length + 1];
double logK = Math.log(k);
for(int i = 0; i < nums.length; i++){
sums[i + 1] = sums[i] + Math.log(nums[i]);
}
int count = 0;
//以i開頭的所有子序列 找到 sums 中小於 logK + sums[i - 1]的最後一個
for(int i = 1; i < sums.length; i++){
int tail = binSearch(sums, i, sums.length - 1, logK + sums[i - 1]);
if(tail == -1){
continue;
}
count += tail - i + 1;
}
return count;
}
public int binSearch(double[] sums, int start, int end, double target){
int mid = 0;
while(start < end - 1){
mid = start + (end - start) / 2;
if(sums[mid] < target){
start = mid;
}else{
end = mid;
}
}
if(sums[end] < target){
return end;
}else if(sums[start] < target){
return start;
}
return -1;
}
解法二:滑動窗口
大體思路爲找到遍歷過程中以當前結點結尾的最長滿足條件的子數組。滑動窗口的左邊記做left,右端記做right。該窗口始終維持一right結尾的滿足條件的最長子數組。遍歷過程中讓right右移,窗內的結點維持:left右移直到該窗類之積小於k。實現代碼如下:
// 方法二 滑動窗
public int numSubarrayProductLessThanK(int[] nums, int k) {
if(k <= 1){
return 0;
}
int left = 0, right = 0;
int prod = 1;
// 依次找到以right結尾的乘積小於k的最長子數組
int count = 0;
for(;right < nums.length; right++){
prod *= nums[right];
while(prod >= k){
prod /= nums[left++];
}
count += right - left + 1;
}
return count;
}
問題二:乘積最大的子數組
問題描述:
給定一整型數組,找到其乘積最大的子數組,返回其最大乘積。
intput : nums = [3 -3 -2 -4 5 0]
output : 40 ,解釋[-2 -4 5]
解決方案:
遍歷過程中依然是找以當前位置結尾的積最大子數組。當該整型數組中全爲正數,若我們已知以nums[i]結尾的最大乘積爲products[i],則以nums[i + 1]結尾的最大乘積有以下兩種情況 :
1)在原先子數組的基礎上添上該元素,
2)以該元素爲新的頭
products[ i + 1] = max(products[i] * nums[i + 1], nums[i + 1])。
但是由於該數組中既有正數,又有負數,除了保存最大的乘積外還需保留最小的乘積。由於負負得正嘛。
productMax[ i + 1] = max(productMax[i] * nums[i + 1], nums[i + 1],productMin[i] * nums[i + 1])
productMax[ i + 1] = min(productMax[i] * nums[i + 1], nums[i + 1],productMin[i] * nums[i + 1])
實現代碼如下:
public int maxProduct(int[] nums) {
int[] productMax = new int[nums.length];
int[] productMin = new int[nums.length];
productMax[0] = nums[0];
productMin[0] = nums[0];
int ans = nums[0];
for(int i = 1; i < nums.length; i++){
productMax[i] = Math.max(nums[i],
Math.max(nums[i] * productMax[i - 1], nums[i] * productMin[i - 1]));
productMin[i] = Math.min(nums[i],
Math.min(nums[i] * productMax[i - 1], nums[i] * productMin[i - 1]));
ans = Math.max(ans, productMax[i]);
}
return ans;
}
我們發現,其後面的最小值和最大值只依賴於前一個的最小值和最大值,因此該問題可以不使用productMax 和productMin連個數組,只使用兩個臨時變量即可。其額外空間複雜度O(N) - > O(1),代碼如下:
public int maxProduct(int[] nums) {
int productMax = nums[0];
int productMin = nums[0];
int ans = nums[0];
for(int i = 1; i < nums.length; i++){
int tempMax = Math.max(nums[i],
Math.max(nums[i] * productMax, nums[i] * productMin));
int tempMin = Math.min(nums[i],
Math.min(nums[i] * productMax, nums[i] * productMin));
productMax = tempMax;
productMin = tempMin;
ans = Math.max(ans, productMax);
}
return ans;
}
此處需要注意的是,對於productMax和productMin需要使用連個臨時變量來賦值,如此爲了避免當前的productMin的計算用的是當前的productMax而不是上一個productMax。