传送门:https://leetcode-cn.com/problems/maximum-product-subarray/submissions/

题目

给你一个整数数组 nums ,请你找出数组中乘积最大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。

示例 1:

输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 有最大乘积 6。


示例 2:**

输入: [-2,0,-1] 输出: 0 解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

解题思路:动态规划

错误思路:

如果我们用 🚩[LeetCode]Dp152 乘积最大子数组 - 图1 来表示以第 🚩[LeetCode]Dp152 乘积最大子数组 - 图2 个元素结尾的乘积最大子数组的乘积,🚩[LeetCode]Dp152 乘积最大子数组 - 图3 表示输入参数 🚩[LeetCode]Dp152 乘积最大子数组 - 图4,那么根据「[
53. 最大子序和**](https://www.yuque.com/qingxuan-u4juc/leetcode/vknvz7)」的经验,我们很容易推导出这样的状态转移方程:

🚩[LeetCode]Dp152 乘积最大子数组 - 图5
它表示以第 🚩[LeetCode]Dp152 乘积最大子数组 - 图6 个元素结尾的乘积最大子数组的乘积考虑 🚩[LeetCode]Dp152 乘积最大子数组 - 图7 是否加入前面的 🚩[LeetCode]Dp152 乘积最大子数组 - 图8 对应的一段,或者单独成为一段,这里两种情况下取最大值。求出所有的 🚩[LeetCode]Dp152 乘积最大子数组 - 图9 之后选取最大的一个作为答案。

可是在这里,这样做是错误的。为什么呢?这里的定义并不满足「最优子结构」。

正确思路:
nums[i]是一个人玩还是跟前面人玩
其实状态转移的关键思想还是一致的,就是我当前的 🚩[LeetCode]Dp152 乘积最大子数组 - 图10 是否考虑加入 🚩[LeetCode]Dp152 乘积最大子数组 - 图11,还是单独成段。

求乘法取最大值会导致每个位置要存储3种状态: [ 原因:**正正可能最大,负负可能最大,自己可能更大** ]

  1. - ![](https://cdn.nlark.com/yuque/__latex/7848225a0136d5fc3d8c3026ed74fd8d.svg#card=math&code=dp_%7B%5Cmax%20%7D%28i%29&height=20&width=60) :以当前位置 ![](https://cdn.nlark.com/yuque/__latex/865c0c0b4ab0e063e5caa3387c1a8741.svg#card=math&code=i&height=16&width=5) 结尾的最大值
  2. - ![](https://cdn.nlark.com/yuque/__latex/3369902b66b1bf43eb0d510aa71a9315.svg#card=math&code=dp_%7B%5Cmi%20%7D%28i%29&height=20&width=57) :以当前位置 ![](https://cdn.nlark.com/yuque/__latex/865c0c0b4ab0e063e5caa3387c1a8741.svg#card=math&code=i&height=16&width=5) 结尾的最小值
  3. - ![](https://cdn.nlark.com/yuque/__latex/f532389f7f2e31677f09a0247da13f72.svg#card=math&code=nums%5Bi%5D&height=20&width=57) :当前位置 ![](https://cdn.nlark.com/yuque/__latex/865c0c0b4ab0e063e5caa3387c1a8741.svg#card=math&code=i&height=16&width=5) 的值

3种状态的原因:取决于 nums[i+1]

  - 若 ![](https://cdn.nlark.com/yuque/__latex/77306cd0d76229a2110f36f529228fc5.svg#card=math&code=nums%5B%5C%20i%2B1%5C%20%5D%3E0&height=20&width=125),那么自然它希望前面的 `dp[i]` 是个最大的正数,这样 `dp[i+1]` 才可能大
     - 若 ,那么自然 `dp[i+1]` 希望自己一个人玩,单独成段取 `nums[i]`
  - 若 ![](https://cdn.nlark.com/yuque/__latex/f206cb9c6f1efd92458579cdf030a6b9.svg#card=math&code=nums%5B%5C%20i%2B1%5C%20%5D%3C0&height=20&width=125),那么自然它希望前面的 `dp[i]` 是个最小的负数,这样 `dp[i+1]` 才可能大

状态转移方程:
**
🚩[LeetCode]Dp152 乘积最大子数组 - 图12

由于每个位置正正可能最大,负负可能最大,自己可能更大,故而三者取大,就是第 个元素结尾的乘积最大子数组的乘积。最后遍历一次dp_{max},找出其中结尾最大的积

代码

错误代码

//算法思想是错误的
public static int maxProduct(int[] nums) {
    if(nums==null||nums.length==0)return 0;
    int[]dp=new int[nums.length];
    int res=dp[0]=nums[0];
    for (int i = 1; i < nums.length; i++) {
        dp[i]=Math.max(dp[i-1]*nums[i],nums[i]);
        res=Math.max(res,dp[i]);//获取dp中的最大值
    }
    return res;
}

我的代码

public class Demo {
    //0放dp[i]的最小值,1放dp[i]的最大值
    public static int maxProduct(int[] nums) {
        if(nums==null||nums.length==0)return 0;
        int res=nums[0];//要返回的结果值
        int[][]dp=new int[nums.length][2];
        for (int i = 0; i < nums.length; i++) {
            if(i==0)dp[i][0]=dp[i][1]=nums[0];
            else {
                dp[i][0]=Math.min(Math.min(dp[i-1][0]*nums[i],dp[i-1][1]*nums[i]),nums[i]);
                dp[i][1]=Math.max(Math.max(dp[i-1][0]*nums[i],dp[i-1][1]*nums[i]),nums[i]);
                res=Math.max(res,dp[i][1]); //结果筛选和状态转移应分离,为了效率合并处理
            }
        }
        return res;
    }

    public static void main(String[] args) {
        System.out.println(maxProduct(new int[]{2,3,-2,4}));
    }
}

🦄大佬代码

class Demo {
    public int maxProduct(int[] nums) {
        // 偶数个负数,整个数组为最大值
        // 奇数个负数,左边、右边取最大者
        int  max = Integer.MIN_VALUE;
        int temp = 1;
        // 从左往右, 取最大值
        for(int i=0; i<nums.length; i++){
            temp = temp * nums[i];
            max = Math.max(max, temp);
            if(nums[i]==0) temp = 1;//0不会影响max,影响Temp
        }
        temp = 1;
        // 从右往左, 取最大值
        for(int i=nums.length-1; i>=0; i--){
            temp = temp * nums[i];
            max = Math.max(max, temp);
            if(nums[i]==0) temp = 1;
        }
        return max;
    }
}

思路解释:

                    ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2955945/1618280890150-fee3766e-e202-4113-95b9-02c83d3f2e4f.png#align=left&display=inline&height=139&margin=%5Bobject%20Object%5D&name=image.png&originHeight=185&originWidth=646&size=9048&status=done&style=none&width=485)

                            ![image.png](https://cdn.nlark.com/yuque/0/2021/png/2955945/1618280901012-d7e0df40-a93c-47ab-ab60-b1ef608c0a93.png#align=left&display=inline&height=118&margin=%5Bobject%20Object%5D&name=image.png&originHeight=157&originWidth=580&size=8340&status=done&style=none&width=435)

这思路其实蛮好的,偶数个负数,整个数组为最大值 [ 不一定,有可能是最大值 ]要考虑是不是有(0..1)小数,但是无关紧要,因为即使有小数也会被 max 更新。偶数个负数,整个数组为有效数组
说明最大值会在整个数组中产生。如图,若负数有偶数个,需要用 max 刷一次整个数组 。
当奇数个负数时,会出现2段有效数组,如图,红色框子和蓝色框子里都是有效数组,超出一定不可能产生结果。因此需要左右遍历一次筛选最终的 max
综上所述,合并起来左右各刷一次就可以。