题目介绍
力扣152题:leetcode-cn.com/problems/ma…
动态规划
看着题目好像跟[53. 最大子序和]类似,于是我们根据「53. 最大子序和」的经验,用 fmax(i)
来表示以第 i
个元素结尾的乘积最大子数组的乘积,a
表示输入参数 nums
,我们很容易推导出这样的状态转移方程:
它表示以第 i
个元素结尾的乘积最大子数组的乘积可以考虑 ai
加入前面的 fmax(i−1)
对应的一段,或者单独成为一段,这里两种情况下取最大值。求出所有的 fmax(i)
之后选取最大的一个作为答案。
因为这里的定义并不满足「最优子结构」。具体地讲,如果 a={5,6,−3,4,−3},那么此时 fmax
对应的序列是 {5,30,−3,4,−3},按照前面的算法我们可以得到答案为 30,即前两个数的乘积,而实际上答案应该是全体数字的乘积。我们来想一想问题出在哪里呢?问题出在最后一个 −3 所对应的 fmax
的值既不是 -3,也不是 4 × −3,而是 5×30×(−3)×4×(−3)。所以我们得到了一个结论:当前位置的最优解未必是由前一个位置的最优解转移得到的。
我们可以根据正负性进行分类讨论。
考虑当前位置如果是一个负数的话,那么我们希望以它前一个位置结尾的某个段的积也是个负数,这样就可以负负得正,并且我们希望这个积尽可能「负得更多」,即尽可能小。如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数,并且希望它尽可能地大。于是这里我们可以再维护一个 fmin(i)
,它表示以第 i
个元素结尾的乘积最小子数组的乘积,那么我们可以得到这样的动态规划转移方程:
它代表第 i
个元素结尾的乘积最大子数组的乘积 fmax(i)
,可以考虑把 ai
加入第 i - 1
个元素结尾的乘积最大或最小的子数组的乘积中,二者加上 ai
,三者取大,就是第 i
个元素结尾的乘积最大子数组的乘积。第 i
个元素结尾的乘积最小子数组的乘积fmin(i)
同理。
代码如下:
class Solution {
public int maxProduct(int[] nums) {
int length = nums.length;
//记录以i为连续尾节点的乘积最大值
int[] maxF = new int[length];
//记录以i为连续尾节点的乘积最小值
int[] minF = new int[length];
maxF[0] = nums[0];
minF[0] = nums[0];
for (int i = 1; i < length; ++i) {
//更新以i为连续尾节点的乘积最大值
maxF[i] = Math.max(maxF[i - 1] * nums[i], Math.max(nums[i], minF[i - 1] * nums[i]));
//更新以i为连续尾节点的乘积最小值
minF[i] = Math.min(minF[i - 1] * nums[i], Math.min(nums[i], maxF[i - 1] * nums[i]));
}
int ans = maxF[0];
for (int i = 1; i < length; ++i) {
//遍历,求出最大值
ans = Math.max(ans, maxF[i]);
}
return ans;
}
}