力扣hot100之152:乘积最大子数组
力扣hot100之152:乘积最大子数组

LeetCode 152. 乘积最大子数组

题目描述

给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组,并返回该子数组所对应的乘积。

这道题和“最大子数组和”看起来有些像,但本质上要更难一些。因为求和的时候,状态变化比较单一,而求乘积时会受到负数和 0 的影响:

  • 一个正数乘上负数,可能让最大值变成最小值;

  • 一个最小的负数乘上负数,反而可能变成最大的正数;

  • 遇到 0 时,前面的连续乘积会被直接断开。

所以这题不能只维护“当前位置结尾的最大乘积”,还必须同时维护“当前位置结尾的最小乘积”。


示例

示例 1

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 的乘积最大,为 6

示例 2

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

解题思路

一、为什么要同时维护最大值和最小值

如果这题只是求“当前位置结尾的最大乘积”,那么在遇到负数时就会出问题。

比如下面这个例子:

nums = [2, 3, -2, 4]

当遍历到 -2 时:

  • 原来的最大乘积乘上 -2,会变成负数;

  • 原来的最小乘积乘上 -2,却有可能变成更大的正数。

再看一个更明显的例子:

nums = [-2, 3, -4]

遍历过程如下:

  • -2 时,最大乘积是 -2,最小乘积也是 -2

  • 3 时,最大乘积是 3,最小乘积是 -6

  • -4 时,原本的最小乘积 -6 乘上 -4 会变成 24

也就是说:

最小值在某些时刻可以帮助我们得到最终的最大值。

因此我们需要维护两个状态:

  • imax:以当前位置结尾的最大乘积

  • imin:以当前位置结尾的最小乘积


二、状态转移

对于当前数字 nums[i],有两种选择:

  • 把它接到前面的连续子数组后面

  • 从它自己重新开始

所以:

imax = Math.max(imax * nums[i], nums[i]);
imin = Math.min(imin * nums[i], nums[i]);

但是这里有一个关键点:

三、如果当前数字是负数,要先交换 imaximin

因为负数会让大小关系翻转:

  • 原本最大的乘积,乘上负数后可能变成最小的

  • 原本最小的乘积,乘上负数后可能变成最大的

所以在更新之前,如果 nums[i] < 0,必须先交换:

if (nums[i] < 0) {
    int tmp = imax;
    imax = imin;
    imin = tmp;
}

这一步是整道题最核心的地方。


代码实现

class Solution {
    public int maxProduct(int[] nums) {
        int max = Integer.MIN_VALUE, imax = 1, imin = 1;
        for(int i=0; i<nums.length; i++){
            if(nums[i] < 0){ 
              int tmp = imax;
              imax = imin;
              imin = tmp;
            }
            imax = Math.max(imax * nums[i], nums[i]);
            imin = Math.min(imin * nums[i], nums[i]);
            max = Math.max(max, imax);
        }
        return max;
    }
}

代码详解

1. 定义三个变量

int max = Integer.MIN_VALUE, imax = 1, imin = 1;
  • max:记录全局最大乘积

  • imax:记录当前位置结尾的最大乘积

  • imin:记录当前位置结尾的最小乘积

这里把 imaximin 初始化为 1,是为了方便后续乘法计算。


2. 遍历整个数组

for(int i = 0; i < nums.length; i++)

对数组中的每个元素,更新当前位置结尾的最大值和最小值。


3. 遇到负数先交换

if(nums[i] < 0){ 
    int tmp = imax;
    imax = imin;
    imin = tmp;
}

因为乘上负数后,最大值和最小值的角色会互换。

例如:

imax = 6, imin = -12, nums[i] = -2

那么:

  • imax * -2 = -12

  • imin * -2 = 24

此时原来的最小值反而能产生更大的结果。


4. 更新当前位置的最大最小乘积

imax = Math.max(imax * nums[i], nums[i]);
imin = Math.min(imin * nums[i], nums[i]);

这表示我们有两种选择:

  • 接上前面的连续子数组

  • 从当前元素重新开始

如果前面的乘积已经不合适了,那么就直接以当前元素作为新的起点。


5. 更新全局答案

max = Math.max(max, imax);

因为 imax 表示当前位置结尾的最大乘积,所以每次遍历完当前位置,都要更新一次全局最大值。


示例推演

我们以数组:

nums = [2, 3, -2, 4]

为例,模拟整个过程。

初始状态

max = -∞, imax = 1, imin = 1

遍历到 2

imax = max(1 * 2, 2) = 2
imin = min(1 * 2, 2) = 2
max = 2

遍历到 3

imax = max(2 * 3, 3) = 6
imin = min(2 * 3, 3) = 3
max = 6

遍历到 -2

先交换:

imax = 3, imin = 6

再更新:

imax = max(3 * -2, -2) = -2
imin = min(6 * -2, -2) = -12
max = 6

遍历到 4

imax = max(-2 * 4, 4) = 4
imin = min(-12 * 4, 4) = -48
max = 6

最终结果为:

6

再看一个更典型的例子

nums = [-2, 3, -4]

遍历到 -2

交换前: imax = 1, imin = 1
交换后: imax = 1, imin = 1
imax = max(1 * -2, -2) = -2
imin = min(1 * -2, -2) = -2
max = -2

遍历到 3

imax = max(-2 * 3, 3) = 3
imin = min(-2 * 3, 3) = -6
max = 3

遍历到 -4

先交换:

imax = -6, imin = 3

再更新:

imax = max(-6 * -4, -4) = 24
imin = min(3 * -4, -4) = -12
max = 24

最终答案是:

24

这个例子就很好地说明了:

维护最小乘积同样非常重要,因为它有可能在遇到负数时翻身变成最大乘积。


复杂度分析

时间复杂度

O(n)

只遍历了一次数组。

空间复杂度

O(1)

只使用了常数个变量。


和最大子数组和问题的区别

很多人会把这题和“最大子数组和”联系起来,但两者有一个重要区别:

  • 最大子数组和只需要维护一个当前最优值

  • 最大乘积子数组必须同时维护最大值和最小值

原因就在于乘积遇到负数时会发生符号翻转,而加法不会。

所以这题虽然也属于动态规划思想,但状态设计要更细一些。


总结

这道题的核心其实只有一句话:

由于负数会让最大值和最小值互换,所以必须同时维护当前位置结尾的最大乘积和最小乘积。

整体解题步骤如下:

  1. imax 记录当前位置结尾的最大乘积

  2. imin 记录当前位置结尾的最小乘积

  3. 遇到负数时先交换两者

  4. 更新当前位置的最大值和最小值

  5. max 维护全局答案

这是一道非常典型的动态规划题,难点不在代码长短,而在于状态设计是否想清楚。

如果能真正理解为什么要维护 imaximin,那么这题就算彻底掌握了。

如果对您有帮助的话,能否支持一下博主?
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇