浅析连续子向量,子数组和(一维,二维)问题

来源:互联网 发布:中国汽车年度销量数据 编辑:程序博客网 时间:2024/06/04 19:11

浅析连续子向量,子数组和(一维,二维)问题

 9月 16 2014 更新日期:9月 16 2014

文章目录
  1. 1. 最大连续子向量和
    1. 1.0.1. 问题描述:
  • 2. 子向量和接近于0
  • 3. 收费站问题
  • 4. 区间赋值问题
  • 5. 二维连续子数组和
  • 6. 参考资料:
  • 最大连续子向量和

    问题描述:

    输入是具有n个浮点数的向量x,输出这个向量的任何连续子向量中的最大和。

    简单分析:子向量可以是一个空向量,空向量的和为0;如果向量的所有元素都是负数,最大子向量和就是0;

    1 简单分析后,对于这个问题,我们立马能向想到的就是暴力算法,对所有0<=i<=j<n的整数对进行迭代。对每个整数对(i,j),程序都要计算x[i…j]的总和,并判断其是否大于当前的最大总和。

    解法1:简单粗暴型int res=0; //答案for(int i= 0 ; i<n;i++)    for(int j=i;j<n;j++)    {        sum=0        for(int k=i;k<=j;k++)             sum+=x[k]        res=max(res,sum)        }

    2 怎么看,上面的算法都是简单粗暴型,O(n^3)的时间复杂度实在不敢恭维,数据量一大,时间上实在不能容忍。那么有没有稍微优雅一点的?我们发现后面的部分有重复计算的,那么我们如何节省它~~一种就是从i开始往前加的时候,每次都记录下来。直接看代码:

    解法2int res=0;  //答案for(int i=0;i<n;i++){    int sum=0;      for(int j=i;j<n;j++)    {        sum+=x[j];  // sum 就是 x[i]至x[j]的和         res=max(res,sum);      }}

    3 上面的代码,虽然比简单粗暴型有一些改进,算法的时间复杂度降为O(n^2),还有一种O(n^2)的算法,令sum(i)表示x[0…i]的总和,然后,x[i] = sum(i) - sum(i-1);

    解法3sum[-1]=0for i=[0,n)    sum[i]=sum[i-1]+x[i]res=0  //储存答案for i=[0,n)    for j=[i,n)        tem=sum[j]-sum[i-1]         res=max(res,tem)

    4 O(n^2)的效率,我们还是觉得不行,可不可以优化一下,好了,我们可以采用分治的思想。要解决规模为n的问题,可递归地解决两个规模近似为n/2的子问题,然后对两个结果进行合并以得到整个问题的答案。将x划分为两个近似相等的子向量ab,在a和b中分别找出总和最大的子向量ma和mb,然后找到跨越a和b边界的最大子向量mc,返回三个总和中的最大者。通过观察发现mc在a中的部分是a中包含右边界的最大子向量,mc在b中的部分是b中包含左边界的最大子向量。伪代码如下:

    解法4:float maxsum(l,u)    if(l>u)  return 0  /* zero elements */    if(l==u)  return max(0,x[1])  /* one element */    m=(l+u)/2    lmax=sum=0;    for(i=m;i>=l;i--)  /* find max crossing to left */        sum+=x[i]        lmax=max(lmax,sum)    rmax=sum=0    for i=(m,u]  /* find max crossing to right */        sum+=x[i]        rmax=max(rmax,sum)    return  max(lmax+rmax,maxsum(l,m),maxsum(m+1,u))

    5 是否可以再优化一下?好了,其实可以。使用扫描算法:我们采用从x[0]开始扫描,一起到最右端x[n-1],并记下所遇到的最大子向量总和(初始值设为0)。假设我们已解决了x[0,i-1]的问题,如何将其扩展到x[0…i]呢?前i个元素中,最大总和子数组要么在前i-1个元素中(用maxsofar存储),要么其结束位置为i(用maxendinghere存储)。

    maxsofar=0maxendinghere=0for i=[0,n)    maxendinghere=max(maxendinghere+x[i],0) /* 计算前maxendinghere是结束位置为i-1的最大子向量的和 */    maxsofar=max(maxsofar,maxendinghere)

    几个重要的算法设计技术:

    • 保存状态,避免重复计算:

    • 将信息预处理至数据结构:   

    • 分治算法:

    子向量和接近于0

    假设我们想要查找的是总和最接近0的子向量,而不是具有最大总和的子向量,该如何设计算法?

    可初始化累加数组cum,使得cum[i]=x[0]+…+x[i]。如果cum[l-1]=cum[u],那么子向量x[l…u]之和就为0.因此可以通过定位cum中最接近的两个元素来找出和最接近0的子向量。这可以通过排序数组,在O(nlogn)时间内完成。


    收费站问题

    问题描述:

    收费公路由n个收费站之间的n-1段公路组成,每一段都和行驶费用挂钩,仅使用费用数组按照O(n)的时间,或者使用具有O(n^2)个项的表按照O(1)的时间描述任意两站之间的费用是无意义的,请设计一个结构,它需要O(n)的空间,但它允许O(1)的时间复杂度求解。

    驶过两个收费站,就是一段公路,汽车在行驶时,只能连续行驶,不会从这段公路跳到后面的公路。所以就符合连续子向量的求和问题。

    可初始化累加数组cum,使得cum[i]=x[0]+…+x[i],

    对于收费站i和j,cum[j] - cum[i-1]就表示在i和j内行驶的路段费用,并且只占用 cum[n]的线性空间。


    区间赋值问题

    对数组array[0…n-1]初始化为全0后,执行n次运算:for i = [l,u] {x[i] += v;},其中l,u,v是每次运算的参数,0<=l<=u<=n-1。直接用这个伪码需要O(n2)的时间,请给出更快的算法。

    初始化y[0,…,n-1]为全0,对每个操作令y[l]+=v和y[u+1]-=v。则结束时x[i]=sigma{k=0 to i}(y[k])。正确性:只需证明每一次执行完操作之后该性质保持不变即可。注意这里的y[i]表示的意义


    二维连续子数组和

    二维数组连续的二维子数组的和怎么求,肯定是一个矩形,我们要遍历吗???
    遍历的话估计复杂度扛不住吧。。如何遍历也是一个问题。

    这个时候我们可以把每一行看成是一个元素,这样就变成了一个纵向的一维数组了。

    这样对一维数组的遍历是和刚才一样的。而对于每一行我们遍历也是和一维是一样的。编码试一试

     //求二维数组的连续子数组之和的最大值  int MaxSum(int (*array)[N])  {      int i,j;      int MaxSum=-INFINITY;//初始化      int imin,imax,jmin,jmax;      for(imin=1;imin<=N;imin++)  {          for(imax=imin;imax<=N;imax++)//当成是遍历纵向的一维  {              for(jmin=1;jmin<=M;jmin++)  {                  for(jmax=jmin;jmax<=M;jmax++)//当成是遍历横向的一维                          MaxSum=MaxNum(MaxSum,PartSum(imin,jmin,imax,jmax));              }  }  }                  return MaxSum;  }  

    时间复杂度(N^2M^2O(PartSum)),如何求部分和PartSum呢?如果这个还是要遍历求的话,复杂度真是不敢看了。。
    求一维的时候我们求Sum[i-j]很好求,可是求二维的时候就变成了四个坐标了,不敢遍历求和了。我们可以先求部分和,把他当作已知的,这个时候遍历求的时候复杂度就是O(1)。
    如何求?我们定义一个部分和数组PartSum,其中PartSum[i][[j]代表了下标(0,0),(0,j),(i,0),(i,j)包围的区间的和。

    而此时下标(imin,jmin),(imin,jmax),(imax,jmin),(imax,jmax)包围的区间和就等于

    PartSum[imax][[jmax]-PartSum[imin-1][[jmax]-PartSum[imax][[jmin-1]+PartSum[imin-1][[jmin-1]。

    这就是我们要求的PartSum(imin,jmin,imax,jmax),接下来就是求PartSum数组了。如何求呢?
    对于每一个PartSum[i][[j]都不是孤立的,都是和其他的有关系的。我们要找出这个关系式

    PartSum[i][[j]=PartSum[i-1][[j]+PartSum[i][[j-1]-PartSum[i-1][[j-1]+array[i][j]。

    这样求可以求出全部的PartSum[i][[j],可是我们不要忽略了一点,PartSum[0][[0]=?对于边界值我们要处理好,而且下标要从1开始。对于PartSum[i][[0]和PartSum[0][[j]都要初始化0,而且array[i][j]的下标也是要-1,因为数组的下标是从0开始的。这是一个办法,不过我们也可以单独求PartSum[i][[0]和PartSum[0][[j]的值,连续相加即可,然后再求其他的也是可以的,空间复杂度也是一样。可是在4重遍历的时候对于PartSum[i][[0]和PartSum[0][[j]我们还是要另外处理,这就比较麻烦了。我们还是用预处理的方法来编码吧。。

        int PartSum[N+1][M+1];      int i,j;      for(i=0;i<=N;i++)          PartSum[i][0]=0;      for(j=0;j<=M;j++)          PartSum[0][j]=0;      for(i=1;i<=N;i++)          for(j=1;j<=M;j++)          PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];

    OK,求得部分和之后我们就开始完善我们的编码了。记住一点,下标(imin,jmin),(imin,jmax),(imax,jmin),(imax,jmax)包围的区间和等于
    PartSum[imax][[jmax]-PartSum[imin-1][[jmax]-PartSum[imax][[jmin-1]+PartSum[imin-1][[jmin-1]。
    编码开始:

    //求二维数组的连续子数组之和的最大值int MaxSum(int (*array)[N]){    int PartSum[N+1][M+1];    int i,j;    for(i=0;i<=N;i++)        PartSum[i][0]=0;    for(j=0;j<=M;j++)        PartSum[0][j]=0;    for(i=1;i<=N;i++)        for(j=1;j<=M;j++)            PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];    int MaxSum=-INFINITY;//初始化    int imin,imax,jmin,jmax;    for(imin=1;imin<=N;imin++)        for(imax=imin;imax<=N;imax++)            for(jmin=1;jmin<=M;jmin++)                for(jmax=jmin;jmax<=M;jmax++)                        MaxSum=MaxNum(MaxSum,PartSum[imax][jmax]-PartSum[imin-1][jmax]-PartSum[imax][jmin-1]+PartSum[imin-1][jmin-1]);    return MaxSum;}

    时间复杂度是O(N^2*M^2),有点坑啊。想一想一维的时候我们用DP来做,这个也可以吗?可以的。我们把每一列看成一个元素。这样对于遍历的行区间,我们就可以当成一维来做。

    对于imin和imax之间的每一列,就相当于一维的一个元素。

    假设这个一维数组是BC,则BC[j]=array[imin][j]+….+array[imax][j],问题就变成了求BC数组的连续子数组之和的最大值了。而根据刚才求的部分和,我们可以知道对于imin行和imax行之间的区间第j列的值是

    BC(PartSum,imin,imax,j)=PartSum[imax][j]-PartSum[imin-1][j]-PartSum[imax][j-1]+PartSum[imin-1][j-1].(此时BC是一个函数)
    OK,编码开始

    //求二维数组的连续子数组之和的最大值int MaxSum(int (*array)[N]){    int PartSum[N+1][M+1];    int i,j;    for(i=0;i<=N;i++)        PartSum[i][0]=0;    for(j=0;j<=M;j++)        PartSum[0][j]=0;    for(i=1;i<=N;i++)        for(j=1;j<=M;j++)            PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];    int MaxSum=-INFINITY;    int Start,All;    int imin,imax;    for(imin=1;imin<=N;imin++)    {        for(imax=imin;imax<=N;imax++)        {            Start=BC(PartSum,imin,imax,M);            All=BC(PartSum,imin,imax,M);            for(j=M-1;j>=1;j--)            {                if(Start>0)                    Start+=BC(PartSum,imin,imax,j);                else                    Start=BC(PartSum,imin,imax,j);                if(Start>All)                    All=Start;            }            if(All>MaxSum)                MaxSum=All;        }    }    return MaxSum;}int BC(int (*PartSum)[N+1],int imin,int imax,int j) //imin--imax第j列的和{    int value;    value=PartSum[imax][j]-PartSum[imin-1][j]-PartSum[imax][j-1]+PartSum[imin-1][j-1];    return value;}

    时间辅助度降到O(NMmin(M,N)),差不多O(N^3)吧。


    参考资料:

    • Programming pearls [推荐]

    • 最大子序列问题

    • 求数组的连续子数组之和的最大值(一维二维)

    0 0
    原创粉丝点击