最大连续区间和算法详解+代码

写在前边

本篇主要记录了最大连续区间和的暴力算法和dp算法(三种写法)

以及讲解了求最大区间和的区间左右下标的方法

-----------------------------------------------------------------------------------

问题概述

这是一个经典的问题。

给定一个长度为n的序列a[1],a[2]...a[n-1],a[n]

求一个连续的子序列 a[i],a[i+1]...a[j-1],a[j],使得a[i]+a[i+1]...a[j-1]+a[j]最大。

暴力枚举法  O(n^2)

我们要求最大的连续区间和,

首先我们知道预处理出前缀和数组可以方便的求出区间和

那么我们再暴力枚举区间左右边界  找出最大的那个区间和就好了

这个暴力的做法很好想

        int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
	}
	for(int i=1;i<=n;i++){//预处理前缀和数组
            sum[i]=sum[i-1]+a[i];
	}
	int flag,ans=0;
	for(int i=1;i<=n;i++){//暴力枚举左右边界
            for(int j=i;j<=n;j++){
                ans=ans>(sum[j]-sum[i-1])?ans:(sum[j]-sum[i-1]);//记录最大值
            }
	}
	printf("%d",ans);

显然 这个n^2的方法不够优秀  难以解决数量较大的数据

所以我们需要进一步优化

动态规划解法  复杂度O(n)     ---------- (三种写法)

我们让dp[ i ]等于 以a[ i ]为结束的 最大连续子段和

因为是以a[ i ]为结束且是连续子段  那么

dp[ i ] 要么就是  a[ i ]本身

          要么 就是a[ i ] + 以a[ i-1 ]为结束的最大连续字段和  也就是 a[ i ] + dp[ i - 1 ]

所以 状态转移方程出来了      dp[i] = max( A[i], dp[i-1]+A[i] )

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn];
ll dp[maxn];
const ll INF=8e18;
ll n,num,x;
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);//输入
    dp[0]=0;
    for(int i=1;i<=n;i++){//状态转移方程
        dp[i]=max(a[i],dp[i-1]+a[i]);
    }
    ll maxn=0;
    for(int i=1;i<=n;i++){//遍历找最大值
        maxn=max(dp[i],maxn);
    }
    printf("%lld\n",maxn);
}

优化常数

这个O(n)的算法  其实仔细算的话(加上输入)  是O(3n)

我们其实可以优化一下常数

输入和记录dp数组以及记录最大值都可以在一遍内完成

这个dp数组  也可以发现  扫描一遍 保留最大值就好了  那么数组也省了  一个变量就够了

最终这个写法也是求解最大连续区间和的标准解法  代码如下

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn];
ll n,ans,dp;
int main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        dp=max(a[i],dp+a[i]);
        ans=max(dp,ans);
    }
    printf("%lld\n",ans);
}

 还有另一种写法 也是dp的思想   复杂度O(n)

我们预处理出前缀和数组  那么sum[j]-sum[i]就是一段区间的和了

那么我们很容易得到  ans = max {  sum[ j ] - min {  sum[ i ]  }  }  ( j > i )

我们只要用一个变量 动态维护一个最小前缀和就好了 

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
ll a[maxn],sum[maxn];
ll n,ans,minn;
int main(){
    scanf("%lld",&n);
    for(int i=1;i<=n;i++){
        scanf("%lld",&a[i]);
        sum[i]=sum[i-1]+a[i];//统计前缀和数组
        ans=max(sum[i]-minn,ans);//动态维护最大区间和
        minn=min(minn,sum[i]);//动态维护最小前缀和
    }
    printf("%lld\n",ans);
}

打印最大区间和的左右下标

例题:hdu1003

链接   传送门

这个题呢   求最大连续区间和  并且打印出来那个区间的左右边界下标

其实呢  很好写  把前边求最大区间和的算法 稍作改动就好了

优化过常数之后的动态规划解法是这样写的

核心的两句就是

  dp=max(a[i],dp+a[i]);
  ans=max(dp,ans);

这两句其实可以用if else语句来写

if(dp>0) dp=dp+a[i];
else dp=a[i];

if(ans>dp)ans=dp
else  ans=ans;

那么这下就很好看出了

那么我们在适当的地方记录下下标就好了

s e  //s为区间左端点  e为区间右端点
t    //t为中间变量  用来存储改变后的区间左端点

int ans=-INF,dp=0,t=1,s=1,e=1;
if(dp>0){
    dp=dp+a[i];
} 
else{
    dp=a[i];t=i;
} 
if(ans>dp){
    ans=dp;
    s=t;e=i
}

 

hdu1003   AC代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<string.h>
using namespace std;
const int INF=0x3f3f3f3f;
int n,T,num,x;
int main(){
    scanf("%d",&T);
    while(T--){
        num++;
        scanf("%d",&n);
        int ans=-INF,dp=0,t=1,e=1,s=1;
        for(int i=1;i<=n;i++){
            scanf("%d",&x);
            if(dp>=0) dp+=x;
            else dp=x,t=i;
            if(dp>ans){
                ans=dp;s=t;e=i;
            }
        }
        cout<<"Case "<<num<<":"<<endl;
        cout<<ans<<" "<<s<<" "<<e<<endl;
        if(T!=0)cout<<endl;
    }
}

 

 

 

 

 

 

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1900_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值