原题描述参考: 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分区且计数负数奇偶数的版本代码简洁很多。时空复杂度也基本一致;