Leetcode学习计划之动态规划入门day6(152, 1567)

目录

152. 乘积最大子数组

问题描述

leetcode53的简单变体?

思路与算法

代码

1567. 乘积为正数的最长子数组长度

问题描述

思路与算法

代码实现


152. 乘积最大子数组

问题描述

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

测试用例的答案是一个 32-位 整数。子数组 是数组的连续子序列。

示例 1:

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

示例 2:

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

提示:

  • 1 <= nums.length <= 2 * 10^4
  • -10 <= nums[i] <= 10
  • nums 的任何前缀或后缀的乘积都 保证 是一个 32-位 整数

leetcode53的简单变体?

        跟昨天的【53.最大子数组和】如出一辙,只不过由和改成了积。对leecode53题解稍作修改可得。

         假设数组的长度是 n,下标从 0 到 n-1。

        我们用f(i)代表以第 i 个数结尾的「连续子数组的最大积」,那么很显然我们要求的答案就是:\max\limits_{0\leq i \leq n-1}{f(i)}

        因此我们只需要求出每个位置的 f(i),然后返回 所得f 数组中的最大值即可。那么我们如何求 f(i) 呢?考虑nums[i]单独成为一段还是加入 f(i-1)对应的那一段,这取决于nums[i]nums[i] * f(i-1)的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:

                    f(i) = max(nums[i], nums[i]*f(i-1))       

        考虑到f(i)  只和 f(i-1) 相关,于是我们可以只用一个变量  来维护对于当前 f(i) 的 f(i-1) 的值是多少,从而让空间复杂度降低到 O(1),这有点类似「滚动数组」的思想。

        但是事情真的这么简单吗?其实不然,因为乘法有个负负得正的效应。比如说,假设输入为[-2,-3,-4,-5],按照以上规则,会得到f(0) = -2, f(1) = 6, f(2) = -4, f(3) = 20。在f(2)的计算中,链条断了。如何解决这一问题?

思路与算法

        对于每个i,求以其结尾的最大连续子数组积和最小连续子数组积。每次都更新两个值,这样就可以把上面所说的那中问题cover进去。

代码

class Solution:
    def maxProduct(self, nums: List[int]) -> int:
        maxprod = minprod = nums[0]
        prevmax = prevmin = nums[0]
        
        for i in range(1,len(nums)):
            curmax = max(prevmax*nums[i], prevmin*nums[i], nums[i])
            curmin = min(prevmax*nums[i], prevmin*nums[i], nums[i])
            maxprod = max(curmax, maxprod)
            minprod = min(curmin, minprod)
            prevmax,prevmin = curmax,curmin
        return maxprod

        执行用时:60 ms, 在所有 Python3 提交中击败了40.03%的用户

        内存消耗:15.7 MB, 在所有 Python3 提交中击败了51.14%的用户

 

1567. 乘积为正数的最长子数组长度

问题描述

给你一个整数数组 nums ,请你求出乘积为正数的最长子数组的长度。

一个数组的子数组是由原数组中零个或者更多个连续数字组成的数组。

请你返回乘积为正数的最长子数组长度。

示例  1:

输入:nums = [1,-2,-3,4]
输出:4
解释:数组本身乘积就是正数,值为 24 。

示例 2:

输入:nums = [0,1,-2,-3,-4]
输出:3
解释:最长乘积为正数的子数组为 [1,-2,-3] ,乘积为 6 。
注意,我们不能把 0 也包括到子数组中,因为这样乘积为 0 ,不是正数。

示例 3:

输入:nums = [-1,-2,-3,0,1]
输出:2
解释:乘积为正数的最长子数组是 [-1,-2] 或者 [-2,-3] 。

提示:

  • 1 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9

思路与算法

        同样要考虑乘法的负负得正的效应。借用上一题的思路。

        假设  数组的长度是 n,下标从 0 到 n-1。

        我们用f(i)代表以第 i 个数结尾的「乘积为正数的最长子数组的长度」,以g(i)代表以第 i 个数结尾的「乘积为负数的最长子数组的长度」,那么很显然我们要求的答案就是:\max\limits_{0 \leq i \leq n-1}{f(i)}

        因此我们只需要求出每个位置的f(i) ,然后返回 所得f 数组中的最大值即可。那么我们如何求 f(i) 呢?当然同时我们还要维护g(i)。 可以得到如下转移方程。

        如果nums[i]大于0,那么f(i) = f(i-1)+1,\ g(i) = g(i-1)+1

        如果nums[i]小于0,那么f(i) = g(i-1)+1, g(i) = f(i-1)+1

        如果 nums[i]等于0,那么f(i) = 0,\ g(i) = 0        

        考虑到f(i)g(i)只和f(i-1)g(i-1)  相关,于是我们可以只用两个来维护历史从而让空间复杂度降低到 O(1),这有点类似「滚动数组」的思想。

        但是以上转移方程有漏洞。当nums[i-1]=0时,虽然在上一步已经将f(i-1)和g(i-1)都置为0了。但是下一个数比如说为正数时,显然g(i)应该还保持为0,而不是变为1。修正以上转移方程如下:

        如果nums[i]大于0,那么:

                f(i)=f(i-1)+1

                g(i)=g(i-1)+1\ \text{if} \ g(i-1)>0 \ \text{else}\ \ 0

        如果nums[i]小于0,那么:

        ​​​​​​​        f(i) = g(i-1)+1,\ \text{if} \ g(i-1)>0 \ \text{else}\ \ 0

                g(i) = f(i-1)+1

        如果 nums[i]等于0,那么f(i) = 0,\ g(i) = 0        

代码实现

class Solution:
    def getMaxLen(self, nums: List[int]) -> int:
        fmax = fprev = int(nums[0] > 0)
        gmax = gprev = int(nums[0] < 0)

        for i in range(1,len(nums)):
            if nums[i]>0:
                fcur = fprev+1
                gcur = gprev+1 if gprev>0 else 0
            elif nums[i]<0:
                fcur = gprev+1 if gprev>0 else 0 
                gcur = fprev+1
            else:
                fcur = gcur = 0
            fmax = max(fmax,fcur)
            gmax = max(gmax,gcur)
            fprev,gprev = fcur,gcur
        return fmax

        执行用时:216 ms, 在所有 Python3 提交中击败了25.16%的用户

        内存消耗:26.2 MB, 在所有 Python3 提交中击败了66.23%的用户

        回到总目录:笨牛慢耕的Leetcode每日一题总目录(动态更新。。。) 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

笨牛慢耕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值