编程之美系列之求子数组的连续最大和

本文探讨了一维数组中连续子数组的最大和问题,分析了常见算法的问题,并提出了改进方案,同时给出了不同情况下的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个有n个整数元素(可以是负数)的一维数组,连续的子数组有很多,那连续的子数组之和的最大值是什么呢?而这个和最大的子数组是哪一段数据呢?下面就让我们来一一的剖析这个问题。首先撇开后面一个问题,不考虑究竟是哪一段数据,只需要求出最大的和就可以了。
1、在说思路之前,先看看网上比较流行的解法,到底这里面有什么问题呢?如果数组里面有正数,OK,那一点问题都没有,比如说{-2,5,3,-6,4,-8,6},其最大值依然是8,但是如果全是负数呢?比如说{-5,-2,-1,-4,-6},正确答案应该是-1,但这里却只能得到0,。很明显算法有问题,或者说这种算法的适用度不是很广。

int ErrorGetMaxSubSum(int *Array, int nLen)
{
	if(nLen < 1 || NULL == Array)
		return Inf;

	int b = 0;
	int sum = 0;
	int i;
	for(i = 0; i < nLen; ++i)
	{
		//如果b<0,说明前面的数会对后面的和起负作用,故重新开始计数
		if(b < 0)
		{
			b = Array[i];
		}
		else
		{
			//正常情况下就叠加
			b += Array[i];
		}
		if(sum < b)
		{
			//如果b加的是正数,这一条一定为真。这样可以排除最后加入几个负数。
			sum = b;
		}
	}
	return sum;
}
2、下面我们来考虑一下几种情况。数组的第一个元素arr[0],以及最大的一段数组arr[i].....arr[j]跟arr[0]之间的关系,有以下几种情况:
*当0=i=j时,元素arr[0]本身构成最大的一段;
*当0=i<j时,和最大的一段从arr[0]开始;
*当0<i时,元素arr[0]和最大的一段没有关系。
OK,那我们用一个sum来动态的记录某一段数据的和,用ans来记录曾经出现过的最大和。那么代码如下:
#include<stdio.h>
const int Inf = -1e5;
inline int max(const int a, const int b)
{
	return a > b ? a : b;
}
//得到数组中的连续最大和
int GetMaxSubSum(int *arr, int nLen)
{
	if(!arr || nLen < 1)
		return Inf;
	int ans = arr[0];
	int sum = arr[0];
	for(int i = 1; i < nLen; ++i)
	{
		if(sum < 0)
			sum = 0;
		sum += arr[i];
		if(ans < sum)
			ans = sum;
	}
	return ans;
}
//上面那个函数的更加精炼的版本
int GetMaxSubSumEx(int *arr, int nLen)
{
	if(!arr || nLen < 1)
		return Inf;
	int ans = arr[0];
	int sum = arr[0];
	for(int i = 1; i < nLen; ++i)
	{
		sum = max(arr[i], sum + arr[i]);
		ans = max(ans, sum);
	}
	return ans;
}
3、如果说在给出最大和的同时,需要知道是哪一段数据构成了这个最大和。那么每一次sum被重置为0的时候,就可能是新的数据段的开始,用temp记下。每一次用sum来更新ans的时候,就说明当前这一段数据是最优的,当前这一段数据是[temp,i],所以更新[b,e]。
//在得到数组最大和的同时,得到是哪一段数据
int GetMaxSubSum(int *arr, int nLen, int &b, int &e)
{
	if(!arr || nLen < 1)
		return Inf;
	int ans = arr[0];
	int sum = arr[0];
	int temp = 0;
	b = e = 0;
	for(int i = 1; i < nLen; ++i)
	{
		if(sum < 0)
		{
			sum = 0;
			temp = i;
		}
		sum += arr[i];
		if(ans < sum)
		{
			ans = sum;
			b = temp;
			e = i;
		}
	}
	return ans;
}
//打印数组
void Print(int *arr, int nLen)
{
	if(!arr|| nLen < 1)
	{
		return;
	}
	int i;
	for(i = 0; i < nLen; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
4、照例,最后给出main函数的调用方法。仅供参考,不需要者可以pass掉。
int main()
{
	const int N = 20;
	int arr[N];
	int i,n,ans;
	int b,e;
	while(scanf("%d", &n) != EOF)
	{
		for(i = 0; i < n; ++i)
			scanf("%d", &arr[i]);
		ans = GetMaxSubSum(arr, n);//方法1的调用
		printf("最大和为:%d\n", ans);
		ans = GetMaxSubSumEx(arr, n);//方法1等价形式的调用
		printf("最大和为:%d\n", ans);
		ans = ErrorGetMaxSubSum(arr, n);//错误方法的调用
		printf("最大和为:%d\n", ans);
		ans = GetMaxSubSum(arr, n, b, e);//方法1扩展的调用
		printf("最大和为:%d\n数据段为:", ans);
		Print(&arr[b], e - b + 1);
	}
}
最后给出几组测试用例的结果,当然其中有一个是调用错误的方法得到的错误结果,一眼就可以看到啦!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值