进阶版最大连续子序列的和:可以删除连续一段

题目描述:

最大连续子序列和世人皆知,现在给定一个序列,求删除连续的一段之后(也可以不删除)的最大连续子序列和。

例如: 1 2 -4 3 3 -4 4 -5 4 -3

删除中间的【-4 4 -5】后最大连续子序列是3 3 4。其最大连续子序列和为10。

分析:

最大连续子序列可以用动态规划很简单的就求出来。

定义f[i]表示以a[i]结尾的最大连续子序列的和。

f[i]=max(f[i-1]+a[i],a[i]);

如果条件没有删除的话,我们现在已经求出了传统的最大连续子序列的和,就是在所有的f[i]中选择最大的。

怎么样利用这个基础的f[i],来求出删除一段后的最大连续子序列呢?

删除一段后的最大子序列的和=删除部分的左边的最大子序列的和+删除部分的右边的最大子序列的和

而删除部分的左边的最大子序列的和恰好可以从f[i]中查询到,右边的,我们只需要构造一个反方向的类似于f[i]功效的数组g[i]即可。

定义g[i]代表从n倒叙到i的最大连续子序列的和。

则g[i]=max(g[i+1]+a[i],a[i]);

有了f[i]和g[i]两个数组,我们就可以枚举删除部分的边界k了。

假设边界是k,我们只需要找出1..k中最大的f[i]以及k+1...n中最大的g[j],然后两个相加就是边界k的所求的最大连续子序列的和。

用样例来说明下:

    初始数据:               1  2  -4  3  3  -4  4  -5  4  -3

    从1到i的最大和f[i]: 1  3  -1  3  6   2  6   1   5   2 

    从n到i的最大和g[i]:5  4   2  6  3   0  4  -1   4  -3 

假设枚举的边界k是8,则8左边(包含k)最大的f[i]是f[5]=6或者f[7]=6,边界8右边最大的g[i]是g[9]=4

把左右两边最大的都加起来就求得了以边界k=8情况下的最大值。

代码:

#include<iostream> 

using namespace std;

int a[100];
int f[100],g[100];
int n,ans;

int main(){
	cin>>n;
	for (int i=1; i<=n; i++)
		cin>>a[i];
	
	for (int i=1; i<=n; i++){
		f[i]=max(f[i-1]+a[i],a[i]);
	}
	for (int i=n; i>=1; i--){
		g[i]=max(g[i+1]+a[i],a[i]);
	}
	
	int Lmax=f[1];
	for (int i=1; i<=n; i++){
		if (f[i]>Lmax) Lmax=f[i];
		int Rmax=g[i+1];
		for (int j=i+1; j<=n; j++)
			Rmax=max(Rmax,g[j]);
		ans=max(ans,Lmax+Rmax);
		//cout<<i<<" "<<Lmax<<" "<<Rmax<<endl;
	}
	cout<<ans<<endl;	
}

优化:

有了f[i] g[i],在求最后的匹配时,上面的方法用了两重循环,复杂度n^2。

其实也可以提前初始化好一个数组,用来查询从i...n的最大值是多少,使之时间复杂度降为O(n)。

定义 s[i]表示从i....n的区间中最大的g[i]。s[i]这个数组的作用就代替了上面的求Rmax的j的循环。

代码:

#include<iostream> 
 
using namespace std;
 
int a[100];
int s[100];
int f[100],g[100];
int n,ans;
 
int main(){
	cin>>n;
	for (int i=1; i<=n; i++)
		cin>>a[i];
	
	for (int i=1; i<=n; i++){
		f[i]=max(f[i-1]+a[i],a[i]);
	}
	for (int i=n; i>=1; i--){
		g[i]=max(g[i+1]+a[i],a[i]);
	}
	
	s[n]=g[n];
	for (int i=n-1; i>=1; i--){
		s[i]=max(s[i+1],g[i]);
	}
	
	int Lmax=f[1];
	for (int i=1; i<=n; i++){
		if (f[i]>Lmax) Lmax=f[i];
		ans=max(ans,Lmax+s[i+1]);
		//cout<<i<<" "<<Lmax<<" "<<s[i+1]<<endl;
	}
	cout<<ans<<endl;	
}

总结:

这个删除部分求最大连续子序列的和,让我想到了凸型的合唱队形那个经典题目。

他们都是把问题分割为两部分,再由两部分拼接成最终的答案。

子序列的问题,通常用动态规划来求解,正序倒序,以及分段求解都是通用技能。

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值