原題描述參考: leetcode’s 152 Maximum Product Subarray
解法1請參考 我前面的文章
動態規劃的解法
動態規劃,是遞歸(或遍歷) + 緩存的技術; 此題一遍遍歷就已經可以得到累積的結果了比較直觀; 解釋在第一個js版本的註釋中
/**
具體例子:
idx: 0, 1, 2, 3, 4 .... n-1
nums: 2, 3, -1, -3, 2 ....
dpMax: 2, 6, -1=max(-6, -1), 18=max(-3*-1, -3*-6), 36=max(,,)
dpMin: 2, 2, -6, -6=min(-3*-1, -3*-6, -6),
初始化:
dpMax[0] = nums[0];
dpMin[0] = nums[0];
由於序列中存在負數也存在整數,所以在遍歷時, 需要保存最大正數和最大負數的求積數組;
而實際上dpMax, dpMin的當前元素的值是從以下3個元素的值中取max或min得到的; 這是從若干種情況下合併狀態得到的(e.g. 當取nums[i]時, 例如具體例子的idx=2時取dpMax取-1); 注意子序列起始點不確定是哪個idx, 所以需要一個max或ans來記錄最後的取值;
dpMax[i-1] = max(nums[i] * dpMax[i-1], nums[i] * dpMin[i-1], nums[i]);
dpMin[i-1] = min(nums[i] * dpMax[i-1], nums[i] * dpMin[i-1], nums[i]);
時間複雜度: O(n)
空間複雜度: O(n)
*/
function maxArrayProduct(nums) {
const dpMax = [];
const dpMin = [];
let max = nums[0];
dpMax[0] = dpMin[0] = nums[0];
const n = nums.length;
for (let i=1; i<n; ++i) {
dpMax[i] = Math.max(nums[i] * dpMax[i-1], nums[i] * dpMin[i-1], nums[i]);
dpMin[i] = Math.min(nums[i] * dpMax[i-1], nums[i] * dpMin[i-1], nums[i]);
max = Math.max(max, dpMax[i]);
}
return max;
}
可以看到dpMax[i]的值只會用到前一個元素的值, 優化方案這個和滾動數組的優化有些類似, 我們不需要用數組(dpMax, dpMin), 每次只需要保存上一次的值和更新這一次的值, 因此可以得到對空間複雜度優化的版本:
function maxArrayProduct(nums) {
let dpMax, dpMin;
let max = nums[0];
dpMax = dpMin = nums[0];
const n = nums.length;
for (let i=1; i<n; ++i) {
let preMax = dpMax;
dpMax = Math.max(nums[i] * dpMax, nums[i] * dpMin, nums[i]);
dpMin = Math.min(nums[i] * preMax, nums[i] * dpMin, nums[i]);
max = Math.max(max, dpMax);
}
return max;
}
C++的版本
#include <iostream>
#include <algorithm>
int maxArrayProduct(const vector<int> &nums) {
int n = nums.size();
int dpMax, dpMin;
int max = nums[0];
dpMax = dpMin = nums[0];
for (int i = 0; i < n; ++i) {
int preMax = dpMax;
dpMax = std::max(nums[i] * dpMin, std::max(nums[i] * dpMax, nums[i]));
dpMin = std::min(nums[i] * preMax, std::max(nums[i] * dpMin, nums[i]));
max = std::max(max, dpMax);
}
return max;
}
可以看到動態規劃的思路的版本,確實比按0分區且計數負數奇偶數的版本代碼簡潔很多。時空複雜度也基本一致;