• 算法与数据结构---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 }

     
  • 相关阅读:
    个人工作总结02
    个人工作总结01
    第七周学习进度条
    构建之法阅读笔记03
    第六周学习进度条
    团队开发介绍
    最大连通子数组的和
    四则运算-安卓版
    第五周学习进度条
    构建之法阅读笔记02
  • 原文地址:https://www.cnblogs.com/Renyi-Fan/p/13023211.html
Copyright © 2020-2023  润新知