【数据结构】 最大连续子序列和 (四种算法逐级优化)

题目:

01-复杂度1 最大子列和问题 (20 分)

给定K个整数组成的序列{ N​1​​, N​2​​, ..., N​K​​ },“连续子列”被定义为{ N​i​​, N​i+1​​, ..., N​j​​ },其中 1≤i≤j≤K。“最大子列和”则被定义为所有连续子列元素的和中最大者。例如给定序列{ -2, 11, -4, 13, -5, -2 },其连续子列{ 11, -4, 13 }有最大的和20。现要求你编写程序,计算给定整数序列的最大子列和。

本题旨在测试各种不同的算法在各种数据情况下的表现。各组测试数据特点如下:

  • 数据1:与样例等价,测试基本正确性;
  • 数据2:102个随机整数;
  • 数据3:103个随机整数;
  • 数据4:104个随机整数;
  • 数据5:105个随机整数;

输入格式:

输入第1行给出正整数K (≤100000);第2行给出K个整数,其间以空格分隔。

输出格式:

在一行中输出最大子列和。如果序列中所有整数皆为负数,则输出0。

输入样例:

6
-2 11 -4 13 -5 -2

输出样例:

20

作者: DS课程组

单位: 浙江大学

时间限制: 50000 ms

内存限制: 64 MB

代码长度限制: 16 KB

 


问题分析:

这道问题就是找出一个已给出的数列中 最大的子列的和。如果当每个子列都为负数时输出为0(即最大子列和为负数时)。

 


解法一 : 穷举法 (复杂度 O(N^3) )

把输入的数组,所有的子列都历遍,并从中找出最大

#include <stdio.h>

int main(){
	int K;//	输入K个数
	scanf("%d",&K);
	int max = 0;
	int a[K] = {0};
	for(int x = 0;x<K;x++)
	{
		scanf("%d",&a[x]);
	}
	
	for(int i=0;i<K;i++){
		for(int j=i;j<K;j++){
			int sum=0;//	求和 
		 	for(int m=i;m<=j;m++){
		 		sum += a[m];
			}
			if(sum > max){
				max = sum;
			}
		}
	}
	if(max < 0) {
		printf("0\n"); 
	}else{
		printf("%d\n",max); 
	}	
	
	return 0;
}

代码思路虽然简单,但是复杂度太大。当样例量级为10000时就超时了。

 

 

解法二: 优化穷举 (复杂度O(N^2) )

第一个算法的第三个for循环中有大量不必要的重复计算,如:计算i到j的和,然而i到j-1的和在前一次的循环中已经计算过,无需重复计算,故该for循环可以去掉

#include <stdio.h>

int main(){
	int K;//	输入K个数
	scanf("%d",&K);
	int max = 0;
	int a[K] = {0};
	for(int x = 0;x<K;x++)
	{
		scanf("%d",&a[x]);
	}
	
	for(int i=0;i<K;i++){
        int sum=0;
		for(int j=i;j<K;j++){
		 	sum += a[m];
			if(sum > max){
				max = sum;
			}
		}
	}
	if(max < 0) {
		printf("0\n"); 
	}else{
		printf("%d\n",max); 
	}	
	
	return 0;
}

虽然能够满足题目要求,但是N^2的复杂度依旧称不上是个好算法。所以还有没有更好的算法呢?

 

解法三: 分治算法 (复杂度O(N*logN))

当遇到N^2 的算法时,应当考虑其能否化成O(N*logN)。

 

算法思想:把问题分成两个大致相等的子问题,然后递归地对它们求解,这是“分”的部分。“治”阶段将两个子问题的解修补到一起并可能再做些少量的附加工作,最后得到整个问题的解。

在该问题中,如果把序列从中间分为两部分,那么最大子序列和可能在三处出现,要么整个出现在输入数据的左半部,要么整个出现在右半部,要么跨越分界线。

前两种情况可以递归求解,第三种情况的最大和可以通过求出前半部分(包括前半部分的最后一个元素)的最大和以及后半部分(包含后半部分的第一个元素)的最大和而得到,此时将两个和相加。

 

//具体实现比较麻烦。

 

 

解法四 :最优解法 --- 在线处理(复杂度 O(N) )

深入问题的本质:极端化

其实,我们注意到, 
当子列和sum 后一个项ai 相加时,只有当sum大于0时,此项加上sum之后才能增大,即才会有扩大子列和的可能。

反之当sum小于零时,可以直接舍弃,将sum变为零。(同时在这里隐含一个条件,就是当所有的项都是负数时,那么sum总是会为0,那么到最后的输出时可以直接输出sum)

#include <stdio.h>

int main(){
	int K;
	scanf("%d",&K);
	int max = 0;
	int a[K] = {0};
	for(int x = 0;x<K;x++)
	{
		scanf("%d",&a[x]);
	}
	int sum = 0;
	for(int i = 0;i < K;i++){
		sum += a[i];
		if(sum > max){
			max = sum; 
		}else if(sum < 0){
			sum = 0;
		}
	}
		printf("%d\n",max); 
	
	return 0;
}


 


 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

12 26 25

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

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

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

打赏作者

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

抵扣说明:

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

余额充值