最大乘积子数组

Maximum Product Subarray

标签(空格分隔): algorithm


涉及到公式请到作业部落查看:Maximum Product Subarray

问题描述

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.

问题来自于Leetcode:Maximum Product Subarray

问题分析

  • 简单来说就是在一个数组中找到一个子数组,使得这个子数组的元素的乘积是所有子数组中最大的。
  • 拿到这个问题,我们看一下和Maximum Subarray类似,但是又有点不同,类似的地方在求解的是一个子数组${A_{i},A_{i+1} \dots A_{j} }$,使得$\prod_{t=i}^{j}A_t$最大。不同的地方在于,这个求解的是子数组各个元素的乘积,我们知道,数组中如果有负数那么有可能存在两个负数相乘,得到的乘积最大,这种情况,和最大子数组不同,最大子数组要相加就可以了。并不需要多考虑这种负数相乘的情况,也就是说,这个题比最大子数组的题多了一种情况,那么我们就把多出的这种情况考虑进来就可以了。

解决方案

1.动态规划方法

  • 按着我们求解最大子数组的思路,来看我们要记录一下以$A_{j}$结尾的最大子数组的乘积,假设用$Smax_{j}$表示,还要记录到$A_{j}$为止的最大子数组的乘积,假设用$Max_{j}$表示。同时,还要考虑负数相乘的情况。那么我们就记录一个以$A_{j}$结尾的最小子数组的乘积,假设用$Smin_{j}$表示,如果遇到$A_{j} \lt 0$这种情况,在整个过程当中 $Smax_j \ge Smin_j$。那么我们直接用$A_{j} $乘以最小子数组$Smin_{j-1}$和$A_{j}$,$Smax_{j-1} \cdot A_{j}$进行比较。取最大的那个。在$A_j \gt 0$的时候,我们也按着这种思路来去三个中最大的那个,那么就不会遗漏任何一种情况了。同时也需要按着跟新最大值的方式来跟新最小值。
  • 下面给出具体的数学表达式。
    $$
    Max_{j} = \left{
    \begin{array}{rcl}
    Smax_{j-1} & & A_j \gt A_{j}\cdot Smax_{j-1} && A_j \gt 0\
    A_{j} \cdot Smin_{j-1} & & A_j \lt 0 && Smin_{j-1}\cdot A_j \gt A_j\
    A_j & & A_j \gt Smax_{j-1} \cdot A_j && A_j \gt Smin_{j-1} \cdot A_j
    \end{array}
    \right.
    $$
  • 简化一下使用max和min函数把多种情况放在一起,这样就可以不用考虑$A_j$的正负了。
    $$
    Smax_j = max( A_j\cdot Smax_{j-1} ,A_j, A_j \cdot Smin_{j-1} )\
    Smin_j = min( A_j\cdot Smin_{j-1} ,A_j, A_j \cdot Smax_{j-1} )\
    Max_j = max(Max_{j-1},Smax_{j})
    $$
  • 下面是按着上面的思路写下的代码。存在优化的地方。
int maxProduct_O3n(vector<int>& nums) {
    int n = nums.size();
    if(n == 0)
        return 0;
    if(n == 1)
        return nums[0];
    vector<int> smax(n,1);
    vector<int> smin(n,1);
    vector<int> dp(n,0);
    smax[0] = nums[0];
    smin[0] = nums[0];
    Max[0] = nums[0];
    for(int i=1;i<n;++i){
        smax[i] = max( max(smax[i-1]*nums[i],nums[i]),smin[i-1]*nums[i] );
        smin[i] = min ( min(smin[i-1]*nums[i],nums[i]) , smax[i-1]*nums[i]);
        Max[i] = max(Max[i-1],smax[i]);
    }
    return Max[n-1];
}
  • 可以看出来时间复杂度为$O(n)$,空间复杂度为$O(3n)=O(n)$.
  • 上面的代码部分明显存在可以优化的部分,比如$Max$数组,$Max_{j-1}$只是在求$Max_{j}$的时候使用到,所以可以用一个变量$dp$代替,在求解$A_j$的时候,$dp$代表到$A_{j-1}$为止的最大子数组乘积的值,并交换更新。
  • 其次,对于$Smax,Smin$两个数组也是,在求$Smax_{j},Smin_{j}$的时候只用到了$Smax_{j-1},Smin_{j-1}$,之前的$j-2,\dots 0$并没有用到。所以分别用两个变量$Smax,Smin$代替即可(命名重复(-__-)b),下面给出优化后的代码。优化后的代码在Leetcode上面跑的比上面的代码快了很多。
int maxProduct(vector<int>& nums) {
    int n = nums.size();
    if(n == 0)
        return 0;
    if(n == 1)
        return nums[0];
    int smax;
    int smin;
    int dp;
    smax = nums[0];
    smin= nums[0];
    dp = nums[0];
    for(int i=1;i<n;++i){
        int tmax = max (  max(smax * nums[i],nums[i]) ,smin * nums[i]);
        smin= min ( min(smin*nums[i],nums[i]) , smax*nums[i]);
        smax = tmax;
        dp = max(dp,smax);
    }
    return dp;
}
  • 时间复杂度仍然为$O(n)$,空间复杂度为$O(1)$.

2.分治的方法

  • 由于这个题是最大子数组和的变形,还没有涉及到扩展到二维数组的情况,二维情况后续会写。所以最大子数组和里面有个分治的算法,在这里面也可以使用。主要的点是,我们要分别在跨越中点的子数组的时候,我们需要考虑到左边最小值和右边最小值,在两者都是负数的情况下,可能乘积也是最大的,所以在求跨越中点的子数组的时候,不仅仅要求的最大的,还要同时求得最小的,并且在取最大值得时候,要考虑左右两个最小的子数组(为负数乘以负数)乘积的情况。
  • 下面给出基于分治算法的递归实现。
int maxProduct(vector<int> & nums) {
    int n = nums.size();
    if(n == 0)
        return 0;
    if(n == 1)
        return nums[0];
    return maxProduct_help(nums,0,n-1);
}
int maxProduct_help(vector<int> &nums,int begin,int end){
    if (begin == end)
        return nums[end];
    
    int mid = (begin + end) >> 1;
     /**左边子数组的最大值子数组乘积的值*/
    int smax_left = maxProduct_help(nums,begin,mid);
     /**右边子数组的最大值子数组乘积的值*/
    int smax_right= maxProduct_help(nums,mid+1,end);
    /**跨越中点的最大值子数组乘积的值*/
    int max_cross = maxCrossMid(nums,begin,end);
    return max(max(smax_left,smax_right),max_cross);
}
/*maxCrossMid函数的时间复杂度实际为O(n)*/
int maxCrossMid(vector<int> & nums,int begin,int end){
    if(begin == end)
        return nums[end];
    int mid = (begin + end ) >> 1;
    int max_left = nums[mid];
    int min_left = nums[mid];
    int max_right = nums[mid+1];
    int min_right = nums[mid+1];
    int sum = 1;
     /*计算以mid结尾的最大(小)的子数组乘积,左边子数组*/
    for(int i=mid;i>=begin;--i){
        sum *= nums[i];
        if(sum > max_left )
            max_left = sum;
        if(sum < min_left)
            min_left = sum;
    }
    sum = 1;
    /*计算以mid+1开始的最大(小)的子数组乘积,右边子数组*/
    for(int i=mid+1;i<=end;++i){
        sum *= nums[i];
        if(sum > max_right)
            max_right = sum;
        if(sum < min_right)
            min_right = sum;
    }
    /*左边最小和右边最小乘积结果可能是比左边最大和右边最大成绩结果要大*/
    return max(min_left*min_right,max_left*max_right);
}
  • 在求解的过程中我们也可以看到$min_left \le max_left$,$min_right \le max_right$,所以并不需要考虑交叉乘积的值最大。
  • 时间复杂度$T(n) = 2T(\frac{n}{2}) + O(n)$,根据主定理,我们可以知道这个解的下界是$O(nlgn)$也就是$\Theta(nlgn)$,但是要比$O(n^2)$要快。

参考内容:

  • 算法导论第4章最大值数组问题
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,740评论 0 33
  • Maximum Subarray 由于简书不支持latex语法,所以可以到下面的作业部落去看。https://ww...
    别时茫茫阅读 2,295评论 0 2
  • 分治策略 本文包括分治的基本概念二分查找快速排序归并排序找出伪币棋盘覆盖最大子数组 源码链接:https://gi...
    廖少少阅读 1,842评论 0 7
  • 算法和数据结构 [TOC] 算法 函数的增长 渐近记号 用来描述算法渐近运行时间的记号,根据定义域为自然数集$N=...
    wxainn阅读 1,060评论 0 0
  • 她是做地产行业的销售,主要是卖中高档写字楼,目前在上海,进公司不到一个月,看着自己离考核阶段还有1个月不到,却迟迟...
    追风筝的秘密阅读 337评论 1 0