23最大乘积子串

   题目描述:给一个浮点数序列,取最大乘积连续子串的值,例如 -2.5,4,0,3,0.5,8,-1,则取出的最大乘积连续子串为3,0.5,8。也就是说,上述数组中,3 0.5 8这3个数的乘积3*0.5*8=12是最大的,而且是连续的。

    提醒:子串子序列不同,子串要求连续,子序列不要求连续。

 

    分析:这个问题与“最大子数组”问题极为相似,最大子数组是求和的最大子串,该题是求积的最大子串。最大子数组中,最巧妙的算法是DP算法,思想以及源代码如下:

 

       先看数组a[0…i],已知它的最大子数组,记为res(i),同时,也可求得包含元素a[i]的最大子数组。记为sum(i)。

       对于数组a[0…i+1],它的最大子数组,要么包含元素a[i+1],要么不包含元素a[i+1]:

       包含元素a[i+1]的情况是:res(i+1) = sum(i+1)

       不包含元素a[i+1]的情况是res(i+1) = res(i)

int DPmaxSum(int *a, int length)

{

         int res = NEINFINITE;

         intsum = 0;

         inti;

        

         for(i= 0; i < length; i++)

         {

                  if(sum>= 0)

                  {

                          sum= sum + a[i];

                  }

                  else

                  {

                          sum= a[i];

                  }

                  res= (sum > res)?sum:res;

         }

         returnres;

}

 

    注意到下面的代码,实际上是需要根据(sum+a[i] >= a[i])?的不同情况做出不同的选择:

                  if(sum >= 0)

                  {

                          sum = sum + a[i];

                  }

                  else

                  {

                          sum = a[i];

                  }

    sum+a[i] ? a[i],在求和的时候,只需要区分sum是否大于或者小于0即可,这是因为对于加法来说,如果a>b,那么对于任意的数a+c > b+c,而不管c是整数还是负数。

    但是对于乘法来说,如果a>b,那么ac和bc的大小并不一定能够确定,因为涉及到负数的问题。

    根据最大子数组的思想,假设包含a[i]的最大乘积为maxi,最小乘积为mini, 所以,mini < maxi,但是maxi* a[i], mini * a[i]与a[i]三个数之间的大小关系也是不确定的。所以,每次求maxi的同时,也需要求mini的大小,代码如下;

double func(double *a,const int n) 

   double *maxA = new double[n]; 

   double *minA = new double[n]; 

   maxA[0] = minA[0] =a[0]; 

   double value = maxA[0]; 

   for(int i = 1 ; i < n ; ++i) 

   { 

        maxA[i] =max(max(a[i],maxA[i-1]*a[i]),minA[i-1]*a[i]); 

        minA[i] =min(min(a[i],maxA[i-1]*a[i]),minA[i-1]*a[i]); 

       value=max(value,maxA[i]); 

   } 

   return  value; 

}

 

    相关问题:给定一个长度为N的整数数组,只允许用乘法,不能用除法,计算任意(N-1)个数的组合中乘积最大的一组,并写出算法的时间复杂度。

    分析:我们可以把所有可能的(N-1)个数的组合找出来,分别计算它们的乘积,并比较大小。由于总共有N个(N-1)个数的组合,总的时间复杂度为O( ),显然这不是最好的解法。下面的解答来自编程之美

解法1

    假设a[1..n]为初始数组,

    s[i]表示从a[1]到a[i]的乘积,记s[0] = 1。所以s[i] = s[i-1] * a[i]。

    t[i]表示从a[i]到a[n]的乘积,记t[n+1] = 1,所以,t[i] = t[i+1]*a[i]。

    p[i]表示除a[i]之外,其他n-1个元素的乘积,所以,p[i] = s[i-1]*t[i+1].

   所以,只要从头至尾扫描一遍,就可求得s[1..n],然后从尾至头,就可求得t[1..n]。进而,线性时间内得到p[1..n],所以,总的时间复杂度为o(n)。

 

解法2
    可以通过分析,进一步减少解答问题的计算量。假设N个整数的乘积为P,针对P的正负性进行如下分析:

 

1:P=0

    说明数组中至少包含有一个0。假设除去一个0之外,其他N-1个数的乘积为Q,根据Q的正负性进行讨论:

    Q为0:说明数组中至少有两个0,那么N-1个数的乘积只能为0,返回0;
    Q为正数:则n-1个数的最大乘积为Q,返回Q

    Q为负数:返回0

 

2P为负数

    因为 正*负=负,所以,可以从N个整数中去掉一个负数,这样其他n-1个数的乘积肯定为一个正数。而要使这个正数最大,这个被去掉的负数必须是数组中最大的(绝对值最小)。因而只需要扫描一遍数组,把绝对值最小的负数给去掉就可以了。

 

3P为正数

    因为 负*负=正 正*正=正。所以,如果数组中存在正数值,那么应该去掉最小的正数值,如果数组中不存在数,则应该去掉最小的负数值(绝对值最大)

 

    上面的解法采用了直接求N个整数的乘积P,进而判断P的正负性的办法,但是直接求乘积在编译环境下往往会有溢出的危险(这也就是本题要求不使用除法的潜在用意),事实上可做一个小的转变,不需要直接求乘积,而是求出数组中正数(+)、负数(-)和0的个数,从而判断P的正负性,其余部分与以上面的解法相同。

    在时间复杂度方面,由于只需要遍历数组一次,在遍历数组的同时就可得到数组中正数(+)、负数(-)和0的个数,以及数组中绝对值最小的负数,绝对值最大的负数,最小的正数。时间复杂度为O(N)。

 

(http://blog.csdn.net/v_july_v/article/details/8701148)

 

posted @ 2015-06-12 09:32  gqtc  阅读(167)  评论(0编辑  收藏  举报