leetcode152. 乘积最大子数组

138 阅读2分钟

题目

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
测试用例的答案是一个 32-位 整数。
子数组 是数组的连续子序列。

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

来源:力扣(LeetCode)
链接:leetcode.cn/problems/ma…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码

法一:动态规划

/**
     * 动态规划;
     * 难点在于对于0和负数的处理,
     * 1。 第一反应可以拆成子数组去做;会增加代码难度
     * 2。 可以改变下参考下l53的题目;表达式改为 dp[i] = Max(dp[i-1]*n[i],n[i]);这样当遇到0之后,就会自动隔断前面的数,从0开始计算
     *     然后在dp[i]数组中取最大值即可
     * @param nums
     * @return
     */
    public int maxProduct(int[] nums) {
        // x:代表从下标。开始;y:代表长度为y
        // 看上面的解释,也就不需要二维了
//        int[][] dp = new int[nums.length][nums.length];
//        for (int i = 0; i < nums.length; i++) {
//            dp[i][1] = nums[i];
//        }
        // 用来存储当下标为i时的最大值
        int maxV[] = new int[nums.length];
        // 因为乘积可能存在负负德正的情况,存储下最小值
        int minV[] = new int[nums.length];
        maxV[0] = nums[0];
        minV[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            // 两个重要的性质:1. Math.max(nums[i],nums[i]*maxV[i-1])这个是为了去掉0;也就是看看是否要从i位置重新开始计算
            //              2. 同时存储最大最小值;是因为可能有负数,负负得正,会得到更大值
            // 最大值的计算:主要就是最大值*当前元素,最小值(负数)*当前元素;取最大值
            maxV[i] = Math.max(Math.max(nums[i],nums[i]*maxV[i-1]),nums[i]*minV[i-1]);
            // 最小值同样道理;当前可能为负数,负负得正,就娶不到这个值了;需要加上max[]的乘机比较
            minV[i] = Math.min(Math.min(nums[i],nums[i]*minV[i-1]),nums[i]*maxV[i-1]);
        }
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < nums.length; i++) {
            max = Math.max(max,maxV[i]);
        }
        return max;
    }

遍历存储法:

public int maxProduct(int[] nums) {
        int min = 1;
        int max = 1;
        int res = Integer.MIN_VALUE;

        for (int i = 0; i < nums.length; i++) {
            // 如果当前数为负数;则交换正负数
            if(nums[i]<0){
                int temp = min;
                min = max;
                max = temp;
            }
            // 然后运用规则,处理0;并存下到当前位置的最大最小值
            max = Math.max(max*nums[i],nums[i]);
            min = Math.min(min*nums[i],nums[i]);

            // 计算最大值
            res = Math.max(res,max);
        }
        return res;
    }

解析

法一:动态规划

题目的主要两个点在于;负数和0的处理
对于0的处理: 用以前遇到过的子数组最大和处理即可,即dp[i] = Math(num[i]*dp[i-1],num[i]);这样当遇到0时,自然会抛弃前面的字符串,从i再开始计算
对于负数的处理: 因为负数的性质在于,前面的乘积如果是负数,虽然不是最大值,但是后面如果再遇到负数,就负负得正,可能是最大值;所以还需要额外存储一下前面的最小值,即:
maxV[i] = Math.max(Math.max(nums[i],nums[i]*maxV[i-1]),nums[i]*minV[i-1]);
maxV[i] = Math.min(Math.min(nums[i],nums[i]*minV[i-1]),nums[i]*maxV[i-1]);

具体看代码

法二:两个变量存储

法二是在leetcode上看到的一个解法,理论一下,只是对于这个处理更加简洁,主要就是把最大最小值存储下来;遇到负数就交换,保证存储的最大最小值是对的;处理0的方法都是一致的 引用:

遍历数组时计算当前最大值,不断更新
令imax为当前最大值,则当前最大值为 imax = max(imax * nums[i], nums[i])
由于存在负数,那么会导致最大的变最小的,最小的变最大的。因此还需要维护当前最小值imin,imin = min(imin * nums[i], nums[i])
当负数出现时则imax与imin进行交换再进行下一步计算

参考:leetcode.cn/problems/ma…