LeetCode | Maximum Product Subarray

100 篇文章 0 订阅
23 篇文章 0 订阅

Find the contiguous subarray within an array (containing at least one number) which has the largest product.

For example, given the array [2,3,-2,4],
the contiguous subarray [2,3] has the largest product = 6.

这道题应当算是第一个独立做出来的DP了~撒花


经历了MLE、TLE和各种WA,终于找到了几乎是最优的解答方式(和Gas Station有点类似)

首先考虑这是一个DP问题。
因为首先,一个区间的最大值可以归结为其子区间的最大值问题,当前做了决策之后对后续不会产生影响(后续可以利用当前计算的结果)


一开始想是dp[i][j]与dp[i+1][j-1]之间的关系,随后想到如何处理负数的问题。
于是开启两个dp数组,一个存最大一个存最小,判断最大的时候也需要判断最小*当前值【因为有可能出现负的】
于是一开始写出的类似
dp1[i][j]=max{dp1[i+1][j-]*nums[j],dp2[i+1][j-1]}
随后问题来了,为何元素的扩展状态是2个方向??
之前回文问题可以这么做是因为回文要求两端相等,而这个问题则需要仔细考虑。
于是写出:
利用dp[i][j]记录从i~j位置乘积的最大值(正数)

dp[i][j]=max{dp[i][j-1]*nums[j],nums[j]}

意思也就是,第j位的元素等于前i~j-1位乘积最大值乘以当前值,如果比当前值大,就更新dp[i][j],否则就继续从j开始计算,dp[i][j]更新为nums[j]

如果遇到负数,那么这个数在将来有可能成为最大的数,所以负数需要额外开辟一个dp数组,专门用于存储最小值,这些最小值(负数)是“有潜力”成为最大值的数。
所以dp方程相应变为


dp[i][j]=max{dp[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]}
dp2[i][j]=min{dp[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]}


到这里终于正确了,不过一运行MLE。。。说明空间申请了太多了,看用例是给了一个大数组,这样n*n肯定是要爆掉的。

于是考虑可能并不需要数组就可以记录?

可以看到dp方程里面,i的值始终未变化,而产生变化的只有j。这说明dp[i][j]并不依赖于i点的状态,而是依赖于j-1点的状态。
所以直接考虑的是每次遍历的时候拿一个maxx和minn分别记录从i到当前位置的最大值。
因为原循环中也仅仅是利用了历史状态,记忆化搜索而已,但这些搜索的结果我们并不是完全都要,只要记录之前记录到的最大值并对它进行更新就好了


// for(int i=0;i<n;i++){
  //     int maxx=0,minn=0;
  //     for(int j=i;j<n;j++){
            int maxv=maxx,minv=minn;
            maxx=maxNum(maxv*nums[j],nums[j],minv*nums[j]);
            minn=minNum(maxv*nums[j],nums[j],minv*nums[j]);
        }
}

这样确实直接省了2个数组,时间复杂度还是O(n^2) 但是空间复杂度由O(n^2)降到了O(1);
very nice;

跑一遍,TLE。。。
说明了什么,说明了二重循环也是没有必要的…
仔细想想,在i=0的时候,其实已经计算了从0开始,到n-1的最大值。
那为何还要计算从非第一个起点开始的呢?不还是遍历整个数组吗?

以此为启发,想到删除里层循环,只保留一个maxx和minn以及记录中间结果的val。

一次遍历,即可解决

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        int n=nums.size();
        if(n==1) return nums[0];

        //选取第i位为结尾的子串作为最大值
        // vector<int> dp1(n,0);
        // vector<int> dp2(n,0);
        // int val=INT_MIN;
        // for(int i=0;i<n;i++){
        //     dp1[i]=maxNum(dp1[i-1]*nums[i],nums[i],dp2[i-1]*nums[i]);
        //     dp2[i]=minNum(dp1[i-1]*nums[i],nums[i],dp2[i-1]*nums[i]);
        // }

        // for(int i=0;i<n;i++)
        // if(dp1[i]>val) val=dp1[i];

        //如果是以i开始j结束为标记,需要n*n大小的标记
        // vector<vector<int> > dp1;
        // vector<vector<int> > dp2;
        // dp1.resize(n,vector<int>(n));
        // dp2.resize(n,vector<int>(n));
        // for(int i=0;i<n;i++){
        //     for(int j=0;j<n;j++){
        //         dp1[i][j]=dp2[i][j]=0;
        //     }
        // }

        //最优解法
        int val=nums[0],maxx=nums[0],minn=nums[0];
        for(int j=1;j<n;j++){

            int maxv=maxx,minv=minn;
            maxx=maxNum(maxv*nums[j],nums[j],minv*nums[j]);
            minn=minNum(maxv*nums[j],nums[j],minv*nums[j]);

            val=maxx>val?maxx:val;
        }

        // int val=INT_MIN;
        // for(int i=0;i<n;i++){
        //     int maxx=0,minn=0;
        //     for(int j=i;j<n;j++){
        //         //超时
        //         int maxv=maxx,minv=minn;
        //         maxx=maxNum(maxv*nums[j],nums[j],minv*nums[j]);
        //         minn=minNum(maxv*nums[j],nums[j],minv*nums[j]);

        //         // 超内存
                   // 记录每一行每一列的的dp[i][j]表示以i开头j结尾的子串的乘积
        //         // dp1[i][j]=maxNum(dp1[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]);
        //         // dp2[i][j]=minNum(dp1[i][j-1]*nums[j],nums[j],dp2[i][j-1]*nums[j]);

        //         val=maxx>val?maxx:val;
        //         // if(nums[i] * nums[j]>0){
        //         //     //这种模拟最长公共子序列的有可能是不连续的
        //         //     dp1[i][j]=dp1[i+1][j-1]*nums[i]*nums[j];
        //         //     dp2[i][j]=dp2[i+1][j-1]*nums[i]*nums[j];
        //         // }
        //         // else if(nums[i] * nums[j]<0){
        //         //     dp1[i][j]=max(dp1[i+1][j-1]*nums[i],dp2[i+1][j-1]*nums[j]);
        //         //     dp2[i][j]=min(dp1[i+1][j-1]*nums[j],dp2[i+1][j-1]*nums[i]);
        //         // }
        //         // else if(nums[i]<0 && nums[j]>0){
        //         //     dp1[i][j]=max(dp2[i+1][j-1]*nums[i],dp1[i+1][j-1]*nums[j]);
        //         //     dp2[i][j]=min(dp2[i+1][j-1]*nums[j],dp1[i+1][j-1]*nums[i]);
        //         // }
        //         // else if(nums[i]*nums[j]==0){

        //         // }
        //     }
        // }
        // output(dp1);
        return val;
        // return getMax(dp1);
    }

    int maxNum(int a,int b,int c){
        int val=a>b?a:b;
        return val>c?val:c;
    }

    int minNum(int a,int b,int c){
        int val=a<b?a:b;
        return val<c?val:c;
    }

    int getMax(vector<vector<int>> &result){
        int val=INT_MIN;
        for(int i=0;i<result.size();i++){
            for(int j=0;j<result[0].size();j++){
                if(result[i][j]>val)
                val=result[i][j];
            }
        }
        return val;
    }

    void output(vector<vector<int>> &result){
        for(int i=0;i<result.size();i++){
            for(int j=0;j<result[i].size();j++){
                printf("%d ",result[i][j]);
            }
            printf("\n");
        }
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值