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