挑战面试编程:最大连续子序列和

本文探讨如何使用动态规划解决寻找数组中最大连续子序列和的问题。通过分析arr[l,m]和arr[m+1,u],我们发现最大和子序列可能跨越arr[m],并且与arr[0,i-1]的情况相似。文章还将讨论以arr[i]结尾的子序列情况。" 79506529,7405261,Spark on YARN配置详解,"['Spark', 'Hadoop', 'YARN', '大数据', '集群配置']
挑战面试编程:最大连续子序列和
问题
对于形如:int arr[] = { 1, -5, 3, 8, -9, 6 };的数组,求出它的最大连续子序列和。
若数组中全部元素都是正数,则最大连续子序列和即是整个数组。
若数组中全部元素都是负数,则最大连续子序列和即是空序列,最大和就是0。

方法一
用sum[i,j]表示arr[i]到arr[j]的和,则显然可以通过枚举(i<=j),求出所有的和。
int maxSubArr(int *arr, int n)
{
	int i, j, k, sum, maxsofar;
	maxsofar = 0;
	for (i = 0; i < n; i++)
		for (j = i; j < n; j++)
		{
			sum = 0;
			for (k = i; k <= j; k++)
				sum += arr[k];
			maxsofar = max(maxsofar, sum);
		}
	return maxsofar;
}
时间复杂度O(n^3)

方法二-a
方法一中,有重复计算的嫌疑,显然sum[i,j]依赖于sum[i,j-1]:sum[i,j]=sum[i,j-1]+arr[j];所以不必每次都arr[i]+arr[i+1]……arr[j]才可得到sum[i,j],只需利用sum[i,j]和sum[i,j-1]的关系即可较少运算。这就是:对过程的解进行记录,以防以后用到,从而减少重复求解。
int maxSubArr(int *arr, int n)
{
	int i, j, k, sum, maxsofar;
	maxsofar = 0;
	for (i = 0; i < n; i++)
	{
		sum = 0;
		for (j = i; j < n; j++)
		{
			sum += arr[j];
			maxsofar = max(maxsofar, sum);
		}
	}
	return maxsofar;
}
时间复杂度O(n^2)


方法二-b
对方法一的另一种优化是:先记录sum[0,j](j=0,1,……,n-1),而sum[i,j]=sum[0,j]-sum[0,i-1];这同样可以减少一层循环。
int maxSubArr(int *arr, int n)
{
	int i, j, k, sum, maxsofar;
	maxsofar = 0;
	int *sumcur = malloc(sizeof(int)*n);
	sumcur[0] = arr[0];
	for (i = 1; i < n; i++)
		sumcur[i] += arr[i];
	for (i = 0; i < n; i++)
	{
		for (j = i; j < n; j++)
		{
			sum = sumcur[j] - sumcur[i] + arr[i];    //[i, j]段的和
			maxsofar = max(maxsofar, sum);
		}
	}
	free(sumcur);
	return maxsofar;
}
时间复杂度依然是O(n^2)。
为防止越界,使用sum[i,j]=sum[0,j]-sum[0,i]+arr[i];代替sum[i,j]=sum[0,j]-sum[0,i-1];

方法三
运用动态规划的思想,先把整个序列等分成两部分:arr[l,m]、arr[m+1,u](l->lower下界,u->upper上界)。
于是,最大和子序列只可能出现在三处:
  1. arr[l,m]
  2. arr[m+1,u]
  3. 最大和子序列跨越arr[m]
对于1,2是相同的子问题,可递归求解。对于3可使用简单的算法直接求解。
int maxsum3(int *arr, int l, int u)
{
	if (l > u) return 0;   //zero elements
	if (l == u) return max(0, arr[l]);   //one element
	int i, j;
	int m = (l + u) / 2;
	int lmax, rmax, sum;
	lmax = rmax = 0;
	sum = 0;
	for (i = m; i >= l; i--)
	{
		sum += arr[i];
		lmax = max(sum, lmax);
	}
	sum = 0;
	for (i = m + 1; i <= u; i++)
	{
		sum += arr[i];
		rmax = max(sum, rmax);
	}
	return max(lmax + rmax, maxsum3(arr, l, m), maxsum3(arr, m + 1, u));
}
int maxSubArr(int *arr, int n)
{
	return maxsum3(arr, 0, n - 1);
}
可以证明它的时间复杂度是O(nlogn)


方法四
该算法只需遍历一次arr,即可得出结果。
逻辑是:
arr[0,i](i=0,1,...,n-1)的连续子序列有很多,特别地考虑其中一种:以arr[i]结尾的子序列。使用maxendinghere表示这种特殊子序列的最大和。我们考虑最大和子序列的组成特点,有且仅有两种:
  1. 与arr[0,i-1]相同。
  2. 以arr[i]结尾。
maxsofar记录的是arr[0,i]的最大和子序列。显然 ,每遍历一个元素,就得更新maxsofar
int maxSubArr(int *arr, int n)
{
	int maxendinghere, maxsofar;
	maxendinghere = maxsofar = 0;
	int i;
	for (i = 0; i < n; i++)
	{
		maxendinghere = max(0, maxendinghere + arr[i]);
		maxsofar = max(maxsofar, maxendinghere);
	}
	return maxsofar;
}
时间复杂度O(n)。


深入思考:如何确定最大和子序列的构成呢?也就是确定arr[i,j]中的i,j。

所有内容的目录
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值