洛谷P1115(最大子段和问题)题解(枚举+枚举优化+分治+dp比较)

P1115 最大子段和

题目描述

给出一段序列,选出其中连续且非空的一段使得这段和最大。算法

输入格式

第一行是一个正整数N,表示了序列的长度。数组

第二行包含NN个绝对值不大于10000的整数Ai,描述了这段序列。ide

输出格式

一个整数,为最大的子段和是多少。子段的最小长度为1。优化

输入输出样例

输入 
7
2 -4 3 -1 2 -4 3
输出
4

说明/提示

【样例说明】spa

2,-4,3,-1,2,-4,3中,最大的子段和为4,该子段为3,-1,2.code

【数据规模与约定】blog

对于40%的数据,有N2000。递归

对于100%的数据,有N200000。input

各个方法介绍:io

纯暴力解法:

这个方法没什么好说的,直接枚举边界而后在累加每个,判断和是否为最大,最大替换便可,算法时间复杂度O(N3)。

代码:

#include <stdio.h>
int a[200000];
int main  (void)
{
	int n, i, j, sum, max, start;
	max = -1e9;
	scanf("%d", &n);
	for(i = 0; i < n;  i++)
		scanf("%d", &a[i]);
	for(i = 0; i < n; i++)
		for(j = i; j < n; j++)
		{
			sum = 0;
			for(start = i; start <= j; start++) 
				sum += a[start];
			if(sum > max)
				max = sum;
		}
		printf("%d", max);
	return 0;
}

 

  结果:

 

 

暴力优化:

因为遍历范围的头尾两个的位置时,尾部要从头开始每次循环完递增,因此,咱们实际上能够在递增时把值加起来,判断是否最大,算法时间复杂度O(N2)

代码:

#include <stdio.h>
int a[200000];
int main  (void)
{
  int n, i, j, sum, max, start;
  max = -1e9;
  scanf("%d", &n);
  for(i = 0; i < n;  i++)
    scanf("%d", &a[i]);   for(i = 0; i < n; i++) {
    sum = 0;     for(j = i; j < n; j++)     {      sum += a[j];   if(sum > max)     max = sum;   }   }   printf("%d", max);   return 0; }

 

  

 

结果:

分治法:

所谓分治,就是强行把数拆成两半,那么,咱们易得,该最大子序列必定是在前半段的最大子序列或者后半段的最大子序列或者是前半段子序列末尾为前半段范围末尾的最大子序列与后半段子序列开头为后半段范围开头的最大子序列之和,只多是这三种状况。咱们采用递归来实现分治,每次程序返回的都是在该段内最大的子序列,最后就能实如今整段内找到最大的子序列。咱们人为的把数组拆成两半,经过递归获得左段和右段的最大值,而后从该段中间向两边遍历,找到从该范围内中间到左边和中间+1到右边的最大子序列,并把二者相加,并把其与左右段最大子序列进行比较并返回最大便可获得该段中最大的子序列。算法时间复杂度O(NlogN)。

代码:

#include <stdio.h>
int a[200000], max;
int maxn(int x, int y, int z)
{
	if(x >= y && x >= z)
		return x;
	else if(y >= x && y >= z)
		return y;
	else
		return z;
}
int divide(int left, int right)
{
	if(left == right)
		return a[left];
	int l = 0, r = 0, maxlt = -1e9, maxrt = -1e9;//maxrt为从中间后一个开始向右最大的子序列,maxlt从中间向左的最大 
	int mid = (right + left) / 2;
	int maxl = divide(left, mid );// maxl为左段最大值 
	int maxr = divide(mid + 1, right);//maxr为右端最大值 
	for(int i = mid; i >= left; i--)
	{
		l += a[i];
		if(l > maxlt)
			maxlt = l;
	}
	for(int i = mid + 1; i <= right; i++)
	{
		r += a[i];
		if(r > maxrt)
			maxrt = r;
	}
	return maxn(maxl, maxr, maxlt + maxrt);
}
int main  (void)
{
	int n, i, j;
	scanf("%d", &n);
	for(i = 0; i < n;  i++)
		scanf("%d", &a[i]);
	max = divide(0, n - 1);
	printf("%d", max);
	return 0;
}

 

  

 

 

结果:

动态规划:

本题将序列从开头开始遍历,至关于序列的开头为数组的开头,序列的末尾从开头一直到末尾,每次获得的结果为该范围内最大的子序列。一开始最大子序列和就是a[0](由于序列开头和结尾都是第一个)。当一个序列中找到最大子序列后,读取下一个数,max要么是以前序列的最大值,要么是以前序列中子序列的末尾是以前序列末尾的最大子序列加上刚刚读取的数要么就是刚刚读取的数。若是以前序列中子序列的末尾为以前序列末尾的最大子序列小于0,那么该子序列加上刚刚读取的数确定没有刚刚读取的数大,若是大于等于0,则该子序列加上刚刚读取的数要比刚刚读的数大,因此二者最大与以前序列最大的子序列之和比较,获得如今的最大子序列和,当到达末尾时,也就是这个数组的最大子序列。声明一个sum累加数,每加一个数都要判断sum是否比max要大,若是大就替换,若是sum < 0, 那么说明今后时的开始累加比此时的数加上以前累加出来的负值要大,所以将sum置为0(代表今后时开始算子序列,sum的值为当前的数),sum的值便是从当前位置向左获得的子序列中和最大的,于是与max进行比较。算法时间复杂度O(N)。

代码:

#include <stdio.h>
int a[200000], sum, max;
int main  (void)
{
	int n, i;
	max = -1e9;
	scanf("%d", &n);
	for(i = 0; i < n;  i++)
	{
		scanf("%d", &a[i]);
		if(sum < 0)//说明以前的序列中,子序列末尾为以前序列末尾的最大子序列之和为负数,加a[i]还不如a[i]大,因此置为0
			sum = 0;
		sum += a[i];
		if(sum > max)
			max = sum;
	}
	printf("%d", max);
	return 0;
}

 

  

 

结果:

其实采用dp的方法并不须要开数组进行存储,这样既节省了空间也节省了时间,这里只须要把a[i]换成一个变量便可(就不贴代码了)

相关文章
相关标签/搜索