Maximum Product Subarray - LeetCode 152

题目描述:
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.

Hide Tags Array Dynamic Programming

分析:
之前做过求最大连续子串的和,采用了动态规划的思想,从前往后到每个元素的最大和。
而此题是给定一个至少含有一个元素的无序整型数组,要求求出最大连续子串的乘积。
方法1、想得比较复杂:
    同样是动态规划的思想:由于乘积会受0元素和负数元素的影响,所以最好是先跳过0元素,将序列先按照0元素分成多个片段,
然后计算每个非零序列的最大子串乘积,这就划分为多个子问题了。而要求一个非零序列的最大子串乘积,需要考虑负数元素的影响:
    1 如果该非零序列中的负数元素个数是偶数,那么问题就简单化了,直接将该序列中的所以非零元素相乘即可。
    2 如果非零序列中含有奇数个负数元素:
       2.1 如果含有1个负数,则用该元素将序列划分为两个子序列(均不包含该负数),然后分别计算两个子序列的乘积,返回较大者即可。
       2.2 如果含有3个及以上负数,则要进行两次分割,即利用第一个负数和最后一个负数分别分割一次(过程和2.1 完全一样)。
      然后返回两次分割中的较大者即可。
在求得每个非零序列的最大子串乘积后,更新整个数组的最大子串乘积为max。
最后max是负数,并且数组中存在0元素,那么max应该更新为0。否则不用更新,直接返回max。
实现过程中,为了方便函数调用,会虚拟A[-1]和A[A.size()]是0,但是在计算非零序列的最大子串乘积时要注意下标的变化,以免越界。

方法2、动态规划
由于可能出现负数,如果当前的元素也刚好是负数,那么久负负得正了。因此需要一个变量保存当前的最小值,那么状态转换方程如下:
当前下标i为最末元素的最大子段乘积为 max_tmp = max(A[i],最小值*A[i],最大值*A[i])

以下是C++实现代码,方法1虽然思路比较复杂,但是效率却比动态规划要高。

附带注释,代码有点长:

class Solution {
public:
    int getmax_Product(vector<int> A,int lef,int rig,int neg_index){ 
    /**计算以下标neg_index分割(lef,rig)为两边的乘积的较大者,lef和rig分别是A中0元素对应的下标,neg_index是负数的下标*/
   
	int max = INT_MIN; // 最大乘积初始化为最小整数

        /**计算区间(lef,neg_index)的元素的乘积,不含0元素,且含偶数个负数*/
        int p1 = A[lef + 1];
	int i = lef + 2;
	while( i < neg_index){
	    p1 *=A[i];
	    i++;
	}

	if(max < p1) //更新最大乘积
	    max = p1;
		
        /**计算区间(neg_index,rig)的元素的乘积,不含0元素,且含偶数个负数*/
        int p2 = A[rig - 1];
	i = rig - 2;
        while(i > neg_index){
	    p2 *=A[i];
	    i--;
        }
	
        if(max < p2)
	        max = p2;			
        return max; 
    }

    int pro_neg(vector<int> A,int lef,int rig){ 
	//计算区间(lef,rig)的最大乘积,lef和rig分别是A中0元素对应的下标
        if(lef + 1 == rig ) //如果是两个连续的0元素,则直接返回结果0
            return 0;        
        int neg_i = -1; //区间内第一个负数的下标,初始化为-1
        int neg_j = -1; //区间中最后一个负数的下标。初始化为-1
        int neg_cnt = 0; //统计区间内负数的个数
        
        for(int i = lef + 1; i < rig; i++){ //寻找第一个负数元素       
            if(A[i] < 0){
                neg_i = i;
                neg_cnt++;
                break;
            }
        }
        for(int i = neg_i + 1; i < rig; i++){ //寻找最后一个负数元素
            if(A[i] < 0){
                neg_j = i;
                neg_cnt++;
            }
        }

        /**当区间中含有非零元素,进行以下处理*/	
        if(neg_cnt % 2 == 0 || neg_i == -1){ 
	//如果区间内负数个数为偶数,直接将(lef,rig)之间的元素相乘,结果必然是区间的最大乘积        
            int p = 1;
            //compute product of (lef,rig)
            for(int i = lef + 1; i < rig; i++){
                p *= A[i];
            }
            return p;
        }
        else{  //区间的的负数个数为奇数       
            if(neg_j == -1){ //只含有一个奇数,只需一次划分计算           
                return getmax_Product(A,lef,rig,neg_i);
            }
            else{ 
		//含有至少三个负数,那么要进行两次划分,分别以第一个和最后一个负数的下标将区间划分,得到最大值返回           
                int p1 = 0, p2 = 0;
                p1 = getmax_Product(A,lef,rig,neg_i);
                p2 = getmax_Product(A,lef,rig,neg_j);
                return p1 > p2 ? p1:p2;
            }
        }
    }
    
    int maxProduct(vector<int> A){
	int n = A.size();
	if(n == 1) //只含有一个元素,则直接返回
	    return A[0];

        int max = INT_MIN;
        vector<int> zero_i; 
        zero_i.push_back(-1); //在A的两端模拟是0元素,以便于调用pro_neg(),以免A中首尾不全是0造成调用失败
        for(int i = 0; i != n; i++){ //将A中0元素的下标放入数组zero_i中        
            if(A[i] == 0)            
		zero_i.push_back(i);					    
        }       
	zero_i.push_back(n); //在A的两端模拟是0元素,以便于调用pro_neg(),以免A中首尾不全是0造成调用失败
	if(zero_i.size() == 2+n) //判断A中是否全是0
            max = 0; 

        int p = 0;
        for(int i = 1; i != zero_i.size(); i++){ //计算每两个0元素之间的元素的最大乘积,同时更新整个数组的连续序列的最大乘积       
            p = pro_neg(A,zero_i[i-1],zero_i[i]); //计算区间(zero[i-1],zero_i[i])不含有0的序列的最大乘积,注意不包含区间首尾元素
            //cout << i << ":" << p << endl;
            if(max < p) 
                max = p;
        }	
	if(zero_i.size() > 2 && max < 0) //如果每个区间中的最大乘积是负数,并且A中含有0元素,所以应该更新max为0
	    max = 0;	
        return max;
    }
};

/**/方法2///8ms///*/
class Solution {
public:
    int maxProduct(vector<int> A) {
        int size= A.size();
        if(size == 0) 
            return 0;
        if(size == 1)
            return A[0];  
        int max_tmp = A[0]; // 记录当前最大值
        int min_tmp = A[0];//记录当前最小值
        int max_p = A[0];
        
        for(int i = 1; i < size; i++){
           int tmp = min_tmp; //每次迭代都要更新当前的最大值和最小值,要注意先保存旧值
           min_tmp = min(A[i],min(tmp * A[i], max_tmp * A[i]));
           max_tmp = max(A[i],max(tmp * A[i], max_tmp * A[i]));
           max_p = max(max_p,max_tmp); //更新全局最大值
        }     
        return max_p;
    }
};


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值