微信扫一扫打赏支持

算法与数据结构---4.3、最大子段和-分治解法

算法与数据结构---4.3、最大子段和-分治解法

一、总结

一句话总结:

最大连续子序列的和有对应的分治解法,因为连续子序列只能是如下三种情况的一种:①完全处于序列的左半、②跨越序列中间、③完全处于序列的右半。取这三种情况里面的最大值,即可得到本题的解。
①完全处于序列的左半:l<=i<=j<=mid
②跨越序列中间:i<=mid<=j<=r
③完全处于序列的右半:mid<=i<=j<=r

#include <iostream>
#include <algorithm>
using namespace std;
int a[200005];
//分治(二分)求最大连续子序列的和
int find(int l,int r){
    if(l==r) return a[l];
    int mid=(l+r)/2;
    //1、计算第二种跨越mid情况的序列的最大和
    //a、求以mid为尾的子序列的最大和
    int maxx1=-0x7fffffff;
    int sum1=0;
    for(int k=mid;k>=l;k--){
        sum1+=a[k];
        maxx1=max(sum1,maxx1);
    }

    //b、求以mid为头的子序列的最大和
    int maxx2=-0x7fffffff;
    int sum2=0;
    for(int k=mid;k<=r;k++){
        sum2+=a[k];
        maxx2=max(sum2,maxx2);
    }

    //2、比较方式1、2、3的最大值
    return max(max(find(l,mid),find(mid+1,r)),maxx1+maxx2-a[mid]);
}

int main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    cout<<find(1,n)<<endl;
    return 0;
}

 

 

 

1、分治(比如本题中的二分)一般用什么算法来实现?

分治一般用递归来做,递归的话,注意递归的终止条件、递归的递推表达式、递归的返回值,就不容易出错了

 

 

2、如何求区间[i..mid]的和的最大值与区间[mid..j]的和的最大值?

求区间[i..mid]的和的最大值与区间[mid..j]的和的最大值,也就是求以mid为尾的子序列的和的最大值 和 以mid为头的子序列的和的最大值
先说以mid为头的子序列的最大和
也就是[mid],[mid...mid+1],[mid...mid+2]......[mid...mid+j]这些序列里面的最大值
int maxx2=-0x7fffffff;
int sum2=0;
for(int k=mid;k<=j;k++){
    sum2+=a[k];
    maxx2=max(sum2,maxx2);
}

求以mid为尾的子序列的最大和
int maxx1=-0x7fffffff;
int sum1=0;
for(int k=mid;k>=i;k--){
    sum1+=a[k];
    maxx1=max(sum1,maxx1);
}

 

 

 

二、最大子段和

博客对应课程的视频位置:4.3、最大子段和-分治解法
https://www.fanrenyi.com/video/27/265

 

1、题目描述

最大子段和(最大连续子序列的和)

题目描述
给出一个长度为 n 的序列 a,选出其中连续且非空的一段使得这段和最大。

输入格式
第一行是一个整数,表示序列的长度 n。
第二行有 n 个整数,第 i 个整数表示序列的第 i 个数字 ai

输出格式
输出一行一个整数表示答案。

输入输出样例
输入
7
2 -4 3 -1 2 -4 3
输出
4

说明/提示
样例解释
选取 [3,5] 子段{3,−1,2}最大,其和为 4。

数据规模与约定
对于40%的数据,保证n<=2×10^3
对于100%的数据,保证1<=n<=2×10^5, -10^4<=a[i]<=10^4

题目提交位置:
P1115 最大子段和 - 洛谷
https://www.luogu.com.cn/problem/P1115

 

2、枚举解法

 1 /*
 2 枚举法
 3 
 4 分析:
 5 我们可以直接按照题目的要求来枚举就好了
 6 
 7 题目的要求是要 求a[1]-a[n]中连续非空的一段的和最大
 8 那么我们把每个连续的一段都枚举出来,然后来算出里面的和,找出最大值即可
 9 
10 所以在这个需求下:
11 我们需要枚举每一段的起点、每一段的终点
12 然后对这一段进行求和
13 
14 枚举变量:每一段的起点、终点
15 枚举范围:起点:1-n,终点:起点-n
16 枚举判断条件:
17 求和得到每一段的和,在这些和里面选出最大的
18 
19 时间复杂度:
20 O(n^3)
21 
22 算法思路:
23 1、枚举每一段的起点和终点
24 2、对每一段进行求和,在这些和里面选出最大的
25 
26 */
27 #include <iostream>
28 using namespace std;
29 int a[200005];
30 int main(){
31     int n;
32     cin>>n;
33     int maxx=-0x7fffffff;
34     for(int i=1;i<=n;i++){
35         cin>>a[i];
36     }
37     //1、枚举每一段的起点和终点
38     for(int i=1;i<=n;i++){
39         for(int j=i;j<=n;j++){
40             //2、对每一段进行求和,在这些和里面选出最大的
41             int sum=0;
42             for(int k=i;k<=j;k++){
43                 sum+=a[k];
44             }
45             if(sum>maxx) maxx=sum;
46         }
47     }
48     cout<<maxx<<endl;
49     return 0;
50 }

 

3、枚举优化

 1 /*
 2 枚举优化
 3 
 4 可以把求和的那层循环去掉,我们可以对数据做预处理
 5 用s[i]表示第一个数到第i个数这个序列的和
 6 
 7 那么求s[i-j](第i个数到第j个数这个序列的和)的时候,
 8 可以直接用s[j]-s[i]+a[i]即可
 9 s[j]-s[i]表示的是i+1到j这个序列的和,所以需要加上a[i]
10 
11 现在的时间复杂度:
12 O(n)+O(n^2)=O(n^2)
13 
14 优化方法:
15 减少重复计算
16 
17 
18 */
19 #include <iostream>
20 using namespace std;
21 int a[200005];
22 int s[200005]={0};
23 int main(){
24     int n;
25     cin>>n;
26     int maxx=-0x7fffffff;
27     for(int i=1;i<=n;i++){
28         cin>>a[i];
29         s[i]=s[i-1]+a[i];
30     }
31     //1、枚举每一段的起点和终点
32     for(int i=1;i<=n;i++){
33         for(int j=i;j<=n;j++){
34             //2、对每一段进行求和,在这些和里面选出最大的
35             int sum=s[j]-s[i]+a[i];
36             if(sum>maxx) maxx=sum;
37         }
38     }
39     cout<<maxx<<endl;
40     return 0;
41 }

 

 

 

4、分治解法

 

  1 /*
  2 
  3 样例
  4 7
  5 2 -4 3 -1 2 -4 3
  6 
  7 分治解法
  8 假定a[1]-a[n]的序列对应的区间[l...r],其中间位置为mid,其最大和的子序列为[i...j]。
  9 那么显然,最大连续子序列的位置只有三种可能:
 10 ①完全处于序列的左半:l<=i<=j<=mid
 11 ②跨越序列中间:i<=mid<=j<=r
 12 ③完全处于序列的右半:mid<i<=j<=r
 13 
 14 
 15 只需要分别求出三种情况下的值,取他们最大的即可。
 16 其中,很容易求出第二种情况,第二种情况也就是包含mid的子序列,
 17 也就是[i...mid...j],而求[i...mid...j]的最大值,
 18 即求出区间[i..mid]的最大值maxx1与区间[mid..j]的最大值maxx2,将其合并即可。
 19 合并之后就变成了[i...mid mid...j],mid出现了两次,要减掉一次
 20 所以[i...mid...j]的最大值就是maxx1+maxx2-mid
 21 
 22 复杂度O(n)
 23 如何处理第一种和第三种情况呢?
 24 也不难发现,
 25 第一种情况,其实就是求区间[l..mid]中的最大值,
 26 第三种情况就是求区间[mid+1..r]中的最大值。那么,只需递归求出即可。
 27 显然,该解法的复杂度为O(nlogn)通过此题是没问题的。
 28 
 29 
 30 算法时间复杂度
 31 O(nlogn):二分是logn,处理第二种情况是n,所以合起来就是O(nlogn)
 32 
 33 
 34 如何求区间[i..mid]的最大值与区间[mid..j]的最大值,
 35 换句话说,也就是如何求以mid为尾的子序列的最大值 和 以mid为头的子序列的最大值
 36 先说以mid为头的子序列的最大和
 37 也就是[mid],[mid...mid+1],[mid...mid+2]......[mid...mid+j]这些序列里面的最大值
 38 int maxx2=-0x7fffffff;
 39 int sum2=0;
 40 for(int k=mid;k<=j;k++){
 41     sum2+=a[k];
 42     maxx2=max(sum2,maxx2);
 43 }
 44 
 45 求以mid为尾的子序列的最大和
 46 int maxx1=-0x7fffffff;
 47 int sum1=0;
 48 for(int k=mid;k>=i;k--){
 49     sum1+=a[k];
 50     maxx1=max(sum1,maxx1);
 51 }
 52 
 53 maxx1+maxx2-a[mid]
 54 
 55 
 56 递归做分治:
 57 a、递归的终止条件:
 58 因为我们的递归是为了求l到r序列的子序列的最大值,
 59 所以当区间只有一个元素时,就是终止条件,那个元素就是子序列的最大值
 60 b、递归的递推表达式:比较方式1、2、3的最大值。第2种跨越mid值的需要我们去计算,1,3种情况又转化成了子问题
 61 c、递归的返回值:子序列的最大和
 62 
 63 
 64 算法步骤:
 65 1、计算第二种跨越mid情况的序列的最大和
 66 2、比较方式1、2、3的最大值
 67 
 68 
 69 
 70 样例:
 71 4
 72 -1 3 -1 -2
 73 结果是3 
 74 
 75 mid=(1+4)/2 2
 76 ①完全处于序列的左半:l...mid:-1 3  对应的是3
 77 ②跨越序列中间:3+3-3=3
 78 ③完全处于序列的右半:mid+1...r:-1 -2 对应的结果是-1
 79 
 80 -1 3
 81 mid=1
 82 ①完全处于序列的左半:l...mid:-1
 83 ②跨越序列中间:-1+2-(-1)=2
 84 ③完全处于序列的右半:mid+1...r:3
 85 
 86 */
 87 #include <iostream>
 88 #include <algorithm>
 89 using namespace std;
 90 int a[200005];
 91 //分治(二分)求最大连续子序列的和
 92 int find(int l,int r){
 93     if(l==r) return a[l];
 94     int mid=(l+r)/2;
 95     //1、计算第二种跨越mid情况的序列的最大和
 96     //a、求以mid为尾的子序列的最大和
 97     int maxx1=-0x7fffffff;
 98     int sum1=0;
 99     for(int k=mid;k>=l;k--){
100         sum1+=a[k];
101         maxx1=max(sum1,maxx1);
102     }
103 
104     //b、求以mid为头的子序列的最大和
105     int maxx2=-0x7fffffff;
106     int sum2=0;
107     for(int k=mid;k<=r;k++){
108         sum2+=a[k];
109         maxx2=max(sum2,maxx2);
110     }
111 
112     //2、比较方式1、2、3的最大值
113     return max(max(find(l,mid),find(mid+1,r)),maxx1+maxx2-a[mid]);
114 }
115 
116 int main(){
117     int n;
118     cin>>n;
119     for(int i=1;i<=n;i++){
120         cin>>a[i];
121     }
122     cout<<find(1,n)<<endl;
123     return 0;
124 }

 

 

 

 

 

 

 
posted @ 2020-06-03 06:39  范仁义  阅读(216)  评论(0编辑  收藏  举报