四种解法——求子序列的最大连续子序和(普通解法、求和解法、分治法、O(n)级解法)(面试经典题)

励志用少的代码做高效表达


在这四种解法里,解法一是通法,可以学到规律和知识,做基础之用;解法二在解法一的基础上做改进,锻炼思维;解法三则是大名鼎鼎的分治法,涉及到递归的知识,算是“高效算法设计”的基础;解法四以O(n)的复杂度解出最大连续子序和,一个字,神奇。四种解法循序渐进,效率逐步提高,精妙至极。建议初学者每种都要掌握。


解法一:最普通的解法,定义i循环,代表数组从0到n-1的值,定义j循环,代表从a[i]开始,到a[j]结束。 定义k循环,将从a[i]至a[j]所有的数相加,最后求出最大子序列和。
时间复杂度:O(n^3)

#include<bits/stdc++.h>
using namespace std;
int main() {
    
    
	int n; cin>>n; int a[n];
	for(int i = 0; i < n; i++) cin>>a[i];
	
	int sum = a[0];		//不能等于0,因为可能是全负序列 
	int temp = 0;
	for(int i = 0; i < n; i++)			 
		for(int j = i; j < n; j++)      //二重循环, 从i截止到j遍历 
			for(int k = i; k <=j; k++) 	//i-j序列的区间和。 
				temp += a[k];  
			sum = max(sum, temp);
		}
	
	cout << sum << endl;
	return 0;
} 

解法二:定义数列S[n],S[i]=a[0]+a[1]+...+a[i]。 若j>i,则S[j]-S[i-1]=a[j]+a[j-1]+...+a[i]。也就是说,嵌套两个for循环, 一个为i,一个为j,就可以表示出任何子序列的和,最后作比较即可。
时间复杂度:O(n^2)

#include<bits/stdc++.h>
using namespace std;
int main() {
    
    
	int n; cin>>n; int a[n];
	for(int i = 0; i < n; i++) cin>>a[i];
	
	int S[n+1]; S[0]=0;
	for(int i = 1; i <= n; i++) S[i] = a[i] + S[i-1];
	
	int best = 0;
	for(int i = 1; i <= n; i++) 
		for(int j = i; j <= n; j++) best = max(best, S[j]-S[i-1]);
		
return 0;}

解法三:分治法,顾名思义,分而治之。

分治算法一般分为以下3个步骤:
一、划分问题:把问题的实例划分成子问题。
二、递归求解:递归解决子问题。
三、合并问题:合并子问题的解得到原问题的解。

最大连续和的分治算法:
1、把序列分成元素个数尽量相等的两半
2、分别求出完全位于左半或者完全位于右半的最佳序列
3、求出起点位于左半、终点位于右半的最大连续和序列,并和子问题的最优解比较。

涉及到递归的代码都比较复杂,但如果实在理解不了,就背下来把,想想小时候背的古诗,就算死记硬背下来,随着时间的推移,也会慢慢懂得其含义的。

时间复杂度:O(nlogn) (这种级别的时间复杂度,一般就可以应付绝大多数的竞赛了)

#include<bits/stdc++.h>
using namespace std;
int a[10005];

int longsub(int *a, int l, int r) {
    
    	//返回数组在左闭右开区间[x,y)中最大连续和
	//为什么不返回a[r]呢? 因为该区间是左闭右开区间 
	if(r-l == 1) return a[l];		//只有一个元素直接返回 
	int mid = l + (r-l)/2;		//分治第一步:划分成[l,m)和[m,r) 
	int maxs = max(longsub(a,l,mid), longsub(a,mid,r));	//分治第二步:递归求解
	
	int v, Lmax, Rmax;
	v=0; Lmax = a[mid-1];		//分治第三步:合并(1)——从分界点开始往左的最大连续和l
	for(int i=mid-1; i>=l; i--) Lmax=max(Lmax, v+=a[i]);
	
	v=0; Rmax=a[mid];			//分治第三步:合并(2)——从分界点开始往右的最大连续和r 
	for(int i=mid; i<r; i++) Rmax=max(Rmax, v+=a[i]);	 
	
	return max(maxs, Lmax+Rmax); 
}

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

解法四:
很特殊的解法,核心思想:当遍历到第i个元素时,判断在它前面的连续子序列和是否大于0,如果大于0,则以位置i结尾的最大连续子序列和为元素i和前门的连续子序列和相加;否则,则以位置i结尾的最大连续子序列和为元素i。
时间复杂度:O(n)

#include<iostream>
using namespace std;
int main() {
    
    
	int n; cin>>n; int a[n];
	for(int i = 0; i < n; i++) cin>>a[i];
	
	int maxsum, maxhere;
	maxsum = maxhere = a[0];	//初始化最大和为a[0]
	for(int i = 1; i < n; i++) {
    
    
		if(maxhere <= 0) maxhere = a[i];   //若前面位置连续子序列和小于0,则以当前位置开始 
		else maxhere += a[i]; //若大于零,则加入
		
		if(maxhere >  maxsum) maxsum = maxhere; 	//更新 
	} 
	cout << maxsum << endl; 
	return 0;
} 

如果这篇文章对你产生了帮助,就请给博主一个赞吧!大家的点赞是我创作的最大动力!

猜你喜欢

转载自blog.csdn.net/weixin_43899069/article/details/108178119