最大连续子序列和:动态规划经典题目(2)

題目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1003

题目大意,给定一个数组,a[0], a[1], ..., a[n-1]需要求它的一个连续子序列,使得这个连续子序列的和最大.

一、暴力求解方法O(n^3)

直观的解法是,遍历所有的连续子序列,取和最大的那个.唯一决定一个连续子序列的指标为序列起始、结束索引,分别设置为i和j则,要求a[i],...,a[j]的和.其中0<=i<n, i<=j<n,如此遍历即可.i和j为两层循环,求和一层循环,共三层循环,复杂度O(n^3),代码如下:

#include <iostream>
using namespace std;

int main()
{
	int T;
	cin >> T;
	while(T--)
	{
		int n, i, j, k;
		cin >> n;
		int *a = new int[n];
		for(i=0;i<n;i++)
			cin >> a[i];
		int maxsum = -9999999;
		int start = 0, end = 0;

		for(i=0;i<n;i++)	//Time complexity: O(n^3)
		{
			for(j=i;j<n;j++)	// pay attention this: j starts from i not i+1
			{	
				int sum = 0;
				for(k=i;k<=j;k++)
				{
					sum += a[k];
				}
				if(sum>maxsum)
				{
					maxsum = sum;
					start = i;
					end = j;
				}
			}

		}
		cout << maxsum << " " << start+1 << " " << end+1 << endl;

		delete [] a;
	}
	
	return 0;
}

二、暴力求解方法O(n^2)

比方法一复杂度低的暴力求解方法,主要技巧是:先求出从a[0]开始到a[i]的和S_i(0<=i<n),一次循环. 再计算S_i+1,j = S_j - S_i, 注意下表的范围.由于不用求和,只需要计算两个值的差值,所以只有i和j循环,两层,复杂度O(n^2).

需要注意下标的问题.代码如下:

#include<iostream>
using namespace std;

int main()
{
	int T;
	cin >> T;
	while(T--)
	{
		int n, i, j;
		cin >> n;
		int *a = new int[n+1];	//pay attention the index: S_i+1,j = S_j - S_i
		int *sum0_i = new int[n+1];

		for(i=1;i<=n;i++)
		{
			cin >> a[i];
			sum0_i[i] = 0;
		}
		sum0_i[0] = 0;
		for(i=1;i<=n;i++)
			sum0_i[i] = sum0_i[i-1] + a[i];
		int maxsum = -99999;
		int start, end;
		for(i=0;i<=n;i++)
		{
			for(j=i+1;j<=n;j++)
			{
				int d = sum0_i[j]-sum0_i[i];
				if(d > maxsum)
				{
					maxsum = d;
					start = i+1;	// start = i+1 not i !!
					end = j;
				}
			}
		}

		cout << maxsum << " " << start << " " << end << endl;

		delete []a;
	}

	return 0;
}

三、暴力求解方法O(n^2)

思路和二差不多,主要省去了求和这一层,技巧是:利用前面求和的结果,而不是重新循环一次.代码如下:

#include<iostream>
using namespace std;

int main()
{
	int T;
	cin >> T;
	while(T--)
	{
		int n, i, j;
		cin >> n;
		int *a = new int[n];
		for(i=0;i<n;i++)	cin >> a[i];

		int maxsum = -99999;
		int start=0, end=0;
		for(i=0;i<n;i++)	// Time complexity: O(n^2)
		{
			int sum = 0;
			for(j=i;j<n;j++)
			{
				sum += a[j];
				if(sum>maxsum)
				{
					maxsum = sum;
					start = i;
					end = j;
				}
			}
		}

		cout << maxsum << " " << start+1 << " " << end+1 << endl;

		delete []a;
	}

	return 0;
}

四、分治法O(nlogn)

不懂

五、累积求和的方法

这种方法最重要的一点是:

给定数组,a[0], a[1], ..., a[i], ...,a[j], ..., a[n-1],设S[i,j] = a[i]+a[i+1]+...+a[j]

如果S[i, j]<0, 那么S[i, j] + a[j+1] < a[j+1], 所以连续子序列{a[i], ..., a[j], a[j+1]}这个子序列的和一定是最大的,所以子序列{a[i], ..., a[j]}没有继续增长的必要,需要从a[j+1]开重新增长子序列

(上面仅供思考,不是严谨的证明)

AC的代码如下:

#include<iostream>
using namespace std;

int main()
{
	int T;
	cin >> T;
	int t = 0;
	while(T--)
	{
		t ++;
		int n, i, j;
		cin >> n;
		int *a = new int[n];
		for(i=0;i<n;i++)
			cin >> a[i];
		int maxsum = -9999;
		int sum = 0;
		int startMax = 0, endMax = -1;
		int startTemp = 0, endTemp = -1;
		for(i=0;i<n;i++)
		{
			sum+=a[i];
			endTemp ++;
			if(sum>maxsum)
			{
				maxsum = sum;
				startMax = startTemp;
				endMax = endTemp;
			}
			if(sum<0)
			{
				sum = 0;
				startTemp = i+1;
				endTemp = i;
			}
		}

		cout << "Case " << t << ":" << endl;
		cout << maxsum << " " << startMax+1 << " " << endMax+1 << endl;
		if(T>0)
			cout << endl;

		delete []a;
	}

	return 0;
}

详细的解答参考:

最大连续子序列和:动态规划经典题目(2)

猜你喜欢

转载自blog.csdn.net/ten_sory/article/details/79776473