152. 乘积最大子数组

96 阅读3分钟

题目介绍

力扣152题:leetcode-cn.com/problems/ma…

image.png

动态规划

看着题目好像跟[53. 最大子序和]类似,于是我们根据「53. 最大子序和」的经验,用 fmax​(i) 来表示以第 i 个元素结尾的乘积最大子数组的乘积,a 表示输入参数 nums,我们很容易推导出这样的状态转移方程:

image.png

它表示以第 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 个元素结尾的乘积最小子数组的乘积,那么我们可以得到这样的动态规划转移方程:

image.png

它代表第 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;
    }
}