浅析连续子向量,子数组和(一维,二维)问题
来源:互联网 发布:中国汽车年度销量数据 编辑:程序博客网 时间:2024/06/04 19:11
浅析连续子向量,子数组和(一维,二维)问题
9月 16 2014 更新日期:9月 16 2014
- 1. 最大连续子向量和
- 1.0.1. 问题描述:
最大连续子向量和
问题描述:
输入是具有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开始往前加的时候,每次都记录下来。直接看代码:
解法2:int 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);
解法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 [推荐]
最大子序列问题
求数组的连续子数组之和的最大值(一维二维)
- 浅析连续子向量,子数组和(一维,二维)问题
- 连续子数组的最大和问题(一维和二维)To the Max (POJ 1050)
- POJ 1050 二维最大连续子向量问题
- 二维数组的连续子数组的最大和
- 求数组的连续子数组之和的最大值(一维二维)
- 求数组的连续子数组之和的最大值(一维二维)
- 最大连续子数组和问题
- 连续子数组最大和问题
- 连续子数组最大和问题
- 连续子数组最大和问题
- 连续子数组最大和问题
- 动态规划问题系列---连续子数组(二维)的最大和
- 面试经典(6)--连续子数组最大和--二维
- 题目1372:最大子向量和(连续子数组的最大和)-九度
- 最大子向量和(连续子数组的最大和)[九度oj1372]
- 最大子向量和(连续子数组的最大和)
- 九度OJ 1372 最大子向量和(连续子数组的最大和)
- 九度OJ 1372 最大子向量和(连续子数组的最大和)
- Android 用Gradle构建你的Android程序
- Java 从基础到进阶学习之路---类编写以及文档注释.
- CVE-2014-6283: Privilege Escalation Vulnerability and Potential Remote Code Execution in SAP Adaptiv
- HDU 4821 String 解题报告(哈希)
- 【学习4】Cocos2d-x 常用控件之Sprite
- 浅析连续子向量,子数组和(一维,二维)问题
- 背包算法问题
- 《Systems Performance: Enterprise and the Cloud》读书笔记系列(三) —— 第二章(二)
- [android]Mac OS环境下真机调试的环境配置
- Android:ListView常见错位之CheckBox错位
- Flume-ng+Kafka+storm的学习笔记
- UVa 442 Matrix Chain Multiplication(矩阵链乘,模拟栈)
- 你的智商几年级,来体验这款小游戏吧!
- leetcode: Gray Code