【leetcode】乘积最大子序列 (线性dp)

42 篇文章 1 订阅
23 篇文章 0 订阅

给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。

示例 1:

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

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

链接:https://leetcode-cn.com/problems/maximum-product-subarray

思路分析:

这道题是求区间最大乘积,算是求最大子列和的变式题吧。
首先,按照最大子列和的做法,**dp[i]表示以i为终点,对于x<=i满足[x,i]是最大乘积。**那么对于dp[i]有两种决策。

  1. 将nums[i]算在连续的区间里,dp[i] = dp[i-1]*nums[i].
  2. 将nums[i]作为一个新起点,dp[i] = nums[i].

两者取最大,*dp[i] = max(dp[i-1]nums[i], nums[i]) 表示以i为终点的区间最大乘积。**再遍历i取dp[i]的最大值就是区间最大乘积终点。
上面这个思路是按照最大子列和来的,不过上面的公式并不准确。
举个例子: -2, 3, -4
按照上面来说,dp[0] = -2, dp[1] = max(-2
3, 3) = 3,dp[2] = max(3
-4, -4) = -4.
这样ans = dp[1] = 3了。可是我们知道这个答案是24.
我们观察dp[1]的判定可以发现,dp[1]通过max将-23=-6这个值消掉了,而我们后面需要用到-6这个值。但是,对于dp[1]来说,当前值3确实是dp[1]的最大乘积。我们又想要-6的值,又想要3的值,这怎么办呢?
一维数组肯定是存不了两个值了。**我们可以通过二维数组重新设计。**对于每个子问题,都有两个状态:所在位置,正负号。
我们可以设dp[n][2]来存储正负号。
令dp[i][0]表示以i为终点的最小区间乘积,并且该乘积为负数。为什么要最小区间乘积呢?因为已经确定该乘积为负数,那么我们希望这个数的绝对值越大越好,这样在以后如果遇到一个负数就可以化成最大的正数了。
令dp[i][1]表示以i为终点的最大区间乘积,并且该乘积为正数。这个不用解释了吧,正数越大越好。
接下来重新分析一下决策问题。
若nums[i] >= 0时:
dp[i][1]有两种选择:以nums[i]为起点;将nums[i]算进连续的区间里。因为nums[i] >= 0,dp[i][1]也规定了其值为正数,因此可以有这两种选择。那么对于dp[i][0]来说,只有一种选择:由上一状态的负数推过来。因为只有上一状态的负数
nums[i]才能保证该状态dp[i][0]为负数。因此写出状态转移方程:

dp[i][1] = max(dp[i-1][1]nums[i], nums[i]). 含义为上一状态的正数nums[i]和以nums[i]为起点选最大。
dp[i][0] = dp[i-1][0]nums[i]. 含义为只能由上一状态的负数nums[i]推过来。

若nums[i] < 0时:
dp[i][0] = min(dp[i-1][0]nums[i], nums[i]). 含义为上一状态负数nums[i]和以nums[i]为起点选最小。
dp[i][1] = dp[i][0]nums[i]. 含义为只能由上一状态的负数nums[i]推过来。

再分析一下初始条件:
若nums[0]>=0, dp[0][1] = nums[0], dp[0][0] = 0.
若nums[0] < 0, dp[0][0] = nums[0], dp[0][1] = 0.

最后,我们再遍历i,取最大的dp[i][1]就是乘积最大子序列。

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        int n = nums.size(), ans = nums[0];
        int dp[n+1][2];
        memset(dp,0,sizeof(dp));
        if(nums[0] >= 0) dp[0][1] = nums[0];
        else dp[0][0] = nums[0];
        for(int i = 1;i < n;i++)
        {
            if(nums[i] < 0)
            {
                dp[i][0] = min(dp[i-1][1]*nums[i], nums[i]);
                dp[i][1] = dp[i-1][0]*nums[i];
            }
            else
            {
                dp[i][0] = dp[i-1][0]*nums[i];
                dp[i][1] = max(dp[i-1][1]*nums[i], nums[i]);
            }
            ans = max(ans,dp[i][1]);
        }
        return ans;
    }
};

空间优化:

因为状态转移方程里都是dp[i] = dp[i-1]… 当前状态的上一状态只有一个并且是相邻的,因此我们可以将4个状态转移方程用两个变量递推,像最大连续子列和那样。
我们定义一个变量p_max表示最大正数区间乘积,代替dp[i][1];n_min表示最小负数区间乘积,代替dp[i][0]. ans = max(ans,p_max).

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        int n = nums.size(), ans = nums[0];
        int p_max, n_min;
        if(nums[0] >= 0) p_max = nums[0], n_min = 0;
        else p_max = 0, n_min = nums[0];
        for(int i = 1;i < n;i++)
        {
            if(nums[i] < 0)
            {
                int tmp = n_min;
                // dp[i][0] = min(dp[i-1][1]*nums[i], nums[i]);
                n_min = min(p_max*nums[i], nums[i]);
                // dp[i][1] = dp[i-1][0]*nums[i];
                p_max = tmp*nums[i];
            }
            else
            {
                // dp[i][0] = dp[i-1][0]*nums[i];
                n_min = n_min*nums[i];
                // dp[i][1] = max(dp[i-1][1]*nums[i], nums[i]);
                p_max = max(p_max*nums[i], nums[i]);
            }
            ans = max(p_max, ans);
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值