最大连续子串问题
首先,要明白子串和子序列的区别。
1)子串(Substring)是串的一个连续的部分;
2)子序列(Subsequence)则是从不改变序列的顺序,而从序列中去掉任意的元素而获得的新序列;(关于求最长子序列的算法见博客:动态规划(5)-字符串相似度算法))
最大连续子串问题是给定一组元素,求使得连续子串的值最大,比如最大连续和子串、最大连续乘积子串。这类问题可以用动态规划求解。下面举例说明如何求解这类问题。
最大连续和子串
例子:输入一个整型数组,数组里有正数也有负数。数组中一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
算法1:时间复杂度O(n*n)- <span style="font-size:18px">maxsofar=pData[0];
- for i=[0,n)
- sum=0;
- for j=[i,n){
- sum+=pData[j];
- maxsofar=max(maxsofar,sum);
- }//end for</span>
1)最优子结构
记f[i]表示以第i个数字结尾的子数组的最大和,那么要求max{f[i]},其中0<=i<n。
假设f[i-1]是i-1时的最优解,则如何求f[i]时的最优解呢?
此时f[i]面临2种选择:一是与前面子数据相加,二是不与前面子数据相加,即f[i]=pData[i]。具体的:
当f[i-1]<=0时,如果把负数与pData[i]相加,得到的结果比pData[i]本身还要小,所以此时f[i]=pData[i]当f[i-1]>0时,则pData[i]与正数相加,得到的结果比pData[i]本身大,所以此时f[i]=f[i-1]+pData[i]
2)迭代解
当i=0或者f[i-1]<=0时,
f[i]=pData[i]
当i!=0并且f[i-1]>0时,
f[i]=f[i-1]+pData[i]
- public int maxSubArray(int[] pData)throws Exception{
- //输入控制
- if(pData==null)
- throw new Exceptin("input can not be null");
- n=pData.length;
- if(n==0)
- throw new Exceptin("have no data,must have one element");
- //只有一个数字的情况
- if(n==1)
- return pData[0];
- //初始化
- int[] f=new int[n]; //用数组f保存计算过程中的值
- f[0]=pData[0];
- int maxsofar=f[0]; //用maxsofar保留当前最大值
- for(int i=1;i<n;i++){
- if(f[i-1]<0)
- f[i]=pData[i];
- else
- f[i]=f[i-1]+pData[i];
- //记录最大值
- maxsofar=max(maxsofar,f[i]);
- }//end for
- return maxsofar;
- }//end maxSubArray()
相关题目:如果希望查找一个总和最接近某一给定实数(如t)的子向量,假设函数absolute(a)用于求a的绝对值,则
absoluteT=absolute(t)
f[i]的值应该满足以下条件:
min{ absolute( absolute(f[i-1]+pData[i]) - absoluteT ), absolute( absolute(pData[i]) - absoluteT) }
所以,有以下递推关系
当absolute( absolute(f[i-1]+pData[i]) - absoluteT )>= absolute( absolute(pData[i]) - absoluteT)时,f[i]=pData[i]
当absolute( absolute(f[i-1]+pData[i]) - absoluteT ) < absolute( absolute(pData[i]) - absoluteT)时,f[i]=f[i-1]+pData[i]
同时用nearT记录f中最接近t的那个总和
最大乘积连续子串
例子:问题:题目描述:给一个浮点数序列,取最大乘积连续子串的值,例如 -2.5,4,0,3,0.5,8,-1,则取出的最大乘积连续子串为3,0.5,8。也就是说,上述数组中,3 0.5 8这3个数的乘积3*0.5*8=12是最大的,而且是连续的。
【算法一】分析:动态规划类似于上述解法,其递归解如下:
当i=0时,f[i]=pData[i]
当i!=0时,
1)当f[i-1]>1时,f[i-1]=f[i-1]*pData[i];
2)当0<f[i-1]<1时,
当pData[i]>=0时,f[i-1]=pData[i];
当pData[i]<0时,f[i-1]=f[i-1]*pData[i];
3)当f[i-1]=0时,
当pData[i]>0时,f[i-1]=pData[i];
当pData[i]<=0时,f[i-1]=f[i-1]*pData[i];
4)当f[i-1]<0时,
当pData[i]>0时,f[i-1]=pData[i];
当pData[i]<0时,f[i-1]=f[i-1]*pData[i];
【算法二】递归公式如下(参考自July最大乘积子串):
假设数组为a[],考虑到可能存在负数的情况,我们用Max来表示以a结尾的最大连续子串的乘积值,用Min表示以a结尾的最小的子串的乘积值,那么状态转移方程为:
Max=max{a, Max[i-1]*a, Min[i-1]*a};
Min=min{a, Max[i-1]*a, Min[i-1]*a};
初始状态为Max[1]=Min[1]=a[1]。
- 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)个数的组合中乘积最大的一组,并写出算法的时间复杂度。
算法:如果允许对数组排序的话,先排序数组,然后从第一个正数开始,转换为求“最大连续乘积子串”的问题。时间复杂度O(n+nlogn)