洛谷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]換成一個變量便可(就不貼代碼了)

相關文章
相關標籤/搜索