152. 乘积最大子数组

在这里插入图片描述
dp的好题,顺带复习了最大连续子数组,dp的难点就是写状态转移方程,这两道题的当前状态只和前一个状态有关。
法1:未优化,因为这是乘积,所以不是简单的找出当前最大的就行了,当前最大的(无论正负)×负数就是最小的了,当前最小的 (无论正负)× 负数就会变成最大的。所以不是简单的和前一个状态的最大值有关和前一个的最小值也有关系。所以开两个记录状态的数组(dp的坏处就是要记录,要额外的空复)一个记录以当前数字为结尾的最大值,一个记录最小值。数组的初始值均为num[0],从1遍历数组,如果当前值是正数,那么以当前数字为结尾的最大值就是以前一个数为结尾的最大值*当前数字 或 当前数字。如果当前值是负数,那么以当前值为结尾的最大值就是以前一个数字为结尾的最小值×当前数字 或 当前数字。最小值数组也是类似。
法2:因为当前状态只和前一个状态有关,所以只要一个fmax和fmin记录前一个状态,再来个res每次和fmax比较。但是要注意的是!划重点:fmax和fmin需要在循环内重新赋值,所以在当前值为负数时,可能出错,用新的最大值去求新的最小值了
这里贴一个无后效性的解释,感觉说的很棒
https://leetcode-cn.com/problems/maximum-product-subarray/solution/dong-tai-gui-hua-li-jie-wu-hou-xiao-xing-by-liweiw/

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        //dp,连续子数组
        if(nums.size() == 0) return 0;
        /*vector<int> maxn(nums.size());
        vector<int> minn(nums.size());
        //必须要有初始值(相当于递归边界?)
        //法1
        minn[0] = maxn[0] = nums[0];
        for(int i = 1; i < nums.size(); ++i){
            if(nums[i] > 0){
                maxn[i] = max(nums[i],maxn[i-1] * nums[i]);
                minn[i] = min(nums[i],minn[i-1] * nums[i]);
            }
            else{
                maxn[i] = max(nums[i],minn[i-1] * nums[i]);
                minn[i] = min(nums[i],maxn[i-1] * nums[i]);
            }
        }
        return *max_element(maxn.begin(),maxn.end());*/

        //法2,优化空间,因为第i个的状态仅和i-1有关,只要保存i-1的状态和最大值即可
        int res = nums[0];
        int fmax = nums[0], fmin = nums[0];
        for(int i = 1; i < nums.size(); ++i){
            int fa = fmax,fi = fmin;
            if(nums[i] > 0){
                fmax = max(nums[i],fa*nums[i]);
                fmin = min(nums[i],fi*nums[i]);
            }
            else{
                fmax = max(nums[i],fi*nums[i]);
                fmin = min(nums[i],fa*nums[i]);
            }
            res = max(fmax,res);
        }
        return res;
    }
};

关键就是可能会用改变后的fmax去更新fmin,这样不行!要在操作前记录下fmax和fmin

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        //至少包含一个数字,那就设初始值为nums[0]
        //要的是连续子数组,就考虑dp,dp[i]记录的是以当前数字为结尾可以形成的最大结果
        //但是有负数可能会很难弄,因为最大的数乘上一个负数可能会让自己变成最小的,而最小的一个数乘上负数就会变成最大的,所以我们要两个dp数组,一个数组记录以当前位为结尾最小的连续数组的积,一个数组记录以当前位为结尾最大的积,两个随时有可能要调换,因为只和前一个状态有关,所以只要两个数字记录即可
        if(nums.size() == 0) return 0;
        int currentMax = nums[0], currentMin = nums[0], res = nums[0];
        for(int i = 1; i < nums.size(); ++i){
            int tmpMax = currentMax, tmpMin = currentMin;
            if(nums[i] > 0){
                currentMax = max(nums[i],tmpMax*nums[i]);
                currentMin = min(nums[i],tmpMin*nums[i]);
            }
            else{
                //这里会有错,因为把currentMax的值给改了,然后求min的时候就错了
                currentMax = max(nums[i],tmpMin*nums[i]);
                currentMin = min(nums[i],tmpMax*nums[i]);
            }
            res = max(res,currentMax);
        }
        return res;
    }
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值