PTA-最大連續子數列和(4種方法)

題目描述

給定K個整數組成的序列{ N1​​ ,N​2 , …, N​k​​ },“連續子列”被定義爲{ Ni​​ , Ni+1, …, Nj},其中 1≤i≤j≤K。“最大子列和”則被定義爲所有連續子列元素的和中最大者。
現要求你編寫程序,計算給定整數序列的最大子列和。

輸入格式

輸入第1行給出正整數n (≤100000);第2行給出n個整數,其間以空格分隔。

輸出格式

在一行中輸出最大子列和。如果序列中所有整數皆爲負數,則輸出0。

樣例輸入

6
-2 11 -4 13 -5 -2

樣例輸出

20






題解

方法①:暴力枚舉法O(n3

最暴力的方法:用三層循環枚舉所有的子數列,輸出其中的最大值。

ans = 0;
for(int i = 1; i <= n; i++)
	for(int j = i; j <= n; j++)
	{
		sum = 0;
		for(int k = i; k <= j; k++)
			sum += num[k];
		if(sum > ans) ans = sum;
	}

稍微優化成O(n2)

可以注意到:設sum(i)爲從 a1 到 ai 的數列的和,sum(0)爲0,則從 ai 到 aj 的子數列的和爲sum( j ) - sum( i-1 )
由此,可以將暴力枚舉法的三層循環改爲二層循環:

ans = 0;
for(int i = 1; i <= n; i++)
	for(int j = i; j <= n; j++)
		if(sum[j] - sum[i-1] > ans)
			ans = sum[j] - sum[i-1]

方法②:二分法 O(nlogn)

首先,我們可以把整個序列平均分成左右兩部分,答案則會在以下三種情況中:
1、所求序列完全包含在左半部分的序列中。
2、所求序列完全包含在右半部分的序列中。
3、所求序列剛好橫跨分割點,即左右序列各佔一部分。
前兩種情況和大問題一樣,只是規模小了些,如果三個子問題都能解決,那麼答案就是三個結果的最大值。

前兩種情況都比較好解決,需要注意的是第三種情況怎麼處理:
以分割點爲起點向左的最大連續序列和、以分割點爲起點向右的最大連續序列和,這兩個結果的和就是第三種情況的答案。因爲起始點(分割點)是固定的,所以兩者只需要O(n)以內的複雜度得出。

long long solve(int left, int right)
{
	int mid = (left + right) / 2;
	//處理第三種情況
	long long suml, sumr, maxl, maxr;
	suml = sumr = maxl = maxr = 0;
	for(int i = mid; i > 0; i--)
	{
		suml += num[i];
		if(suml>maxl) maxl = suml;
	}
	for(int i = mid+1; i < n; i++)
	{
		sumr += num[i];
		if(sumr>maxr) maxr = sumr;
	}
	long long ans = maxr + maxl;
	
	long long lans = solve(left, mid), rans = solve(mid+1, right); //處理左右兩段
	ans = max(ans, lans); ans = max(ans, rans); //返回最大值
	return ans;
}

方法③ 動態規劃O(n)

終極王牌方法,找到一個合適的公式,可以很快解決問題。
根據連續子數列的連續這一性質,我們可以設dp[i]爲以num[i]結尾的最大子數列和,則有dp[i]=max(dp[i1],  0)+num[i]dp[i] = max(dp[i-1], \ \ 0)+num[i]

而最終答案則是: max(dp[i]),i[1, n]max(dp[i]),i\in[1,\ n]

dp[0] = 0; 
long long ans = 0;
for(int i = 1; i <= n; i++)
{
	dp[i] = max(dp[i-1], 0) + num[i];
	if(dp[i] > ans) ans = dp[i];
}
cout << ans;

方法④ 另一種O(n)算法

由暴力法的前綴和法得到啓發:以num[i]結尾的某個子數列和,是sum[i] - sum[j](j < i),則以num[i]結尾的子數列和,是sum[i] - sum[ j-1 ],其中sum[ j-1 ]爲sum[0], sum[1] , …, sum[ i-1 ]中的最小值。
則我們只需要在一次遍歷中維護之前的最小的sum不斷更新答案即可。

long long minsum = 0, ans = 0, sum = 0;
for(int i = 1; i <= n; i++)
{
	sum += num[i];
	if(sum - minsum > ans) ans = sum - minsum;
	if(sum < minsum) minsum = sum;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章