博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅析连续子向量,子数组和(一维,二维)问题
阅读量:5022 次
发布时间:2019-06-12

本文共 6589 字,大约阅读时间需要 21 分钟。

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

文章文件夹

最大连续子向量和

问题描写叙述:

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

简单分析:子向量能够是一个空向量,空向量的和为0;假设向量的全部元素都是负数,最大子向量和就是0;

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

解法1:简单粗暴型int res=0; //答案for(int i= 0 ; i

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

解法2:int res=0;  //答案for(int i=0;i

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

解法3:sum[-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 [推荐]

转载于:https://www.cnblogs.com/blfshiye/p/4084948.html

你可能感兴趣的文章
判断CPU大小端示例代码
查看>>
ARTS打卡第13周
查看>>
循环队列的运用---求K阶斐波那契序列
查看>>
pta 编程题14 Huffman Codes
查看>>
初始化bootstrap treeview树节点
查看>>
python selenium向<sapn>标签中写入内容
查看>>
JS常用坐标
查看>>
使用”结构化的思考方式“来编码和使用”流程化的思考方式“来编码,孰优孰劣?...
查看>>
C#调用斑马打印机打印条码标签(支持COM、LPT、USB、TCP连接方式和ZPL、EPL、CPCL指令)【转】...
查看>>
关于git的认证方式
查看>>
字符串按照字典序排列
查看>>
IOS 开发调用打电话,发短信
查看>>
CI 框架中的日志处理 以及 404异常处理
查看>>
keepalived介绍
查看>>
css3 标签 background-size
查看>>
python itertools
查看>>
Linux内核调试技术——jprobe使用与实现
查看>>
样式、格式布局
查看>>
ubuntu设计文件权限
查看>>
Vue双向绑定原理详解
查看>>