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

最大连续子序列和问题

给定k个整数的序列{N1,N2,...,Nk },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= k。最大连续子序列是所有连续子序中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{11,-4,13},最大连续子序列和即为20。

注:为方便起见,如果所有整数均为负数,则最大子序列和为0。

解决这样一个问题是一个很有趣的过程,我们可以尝试着从复杂度比较高的算法一步一步地推出复杂度较低的算法。

算法一:

时间复杂度:O(N^3)

其代码:

int MaxSubSequence(const int A[], int N){
	int ThisSum,MaxSum,i,j,k;
	MaxSum = 0;
	for(i=0;i<N;i++)
	{
		for(j=i;j<N;j++)
		{
			ThisSum = 0;
			for(k=i;k<=j;k++)
			{
				ThisSum += A[k];
			}
			if(ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
	return MaxSum;
} 

对于此种算法,其主要方法是穷举法,即求出该序列所有子序列的序列和,然后取最大值即可。

算法二:

时间复杂度:O(N^2)

其代码:

int MaxSubSequence(const int A[], int N){
	int ThisSum,MaxSum,i,j;
	MaxSum = 0;
	for(i=0;i<N;i++)
	{
		ThisSum = 0;
		for(j=i;j<N;j++)
		{
			ThisSum += A[j];
			if(ThisSum > MaxSum)
				MaxSum = ThisSum;
		}
	}
	return MaxSum;
}

对于这种方法,归根究底还是属于穷举法,其间接地求出了所有的连续子序列的和,然后取最大值即可。

那么,这里,我们需要对比一下前面两种算法,为什么同样都是穷举法,但算法一的时间复杂度远高于算法二的时间复杂度?

算法二相较于算法一,其优化主要体现在减少了很多重复的操作。

对于A-B-C-D这样一个序列,

算法一在计算连续子序列和的时候,其过程为:

A-B、A-C、A-D、B-C、B-D、C-D

而对于算法二,其过程为:

A-B、A-C、A-D、B-C、B-D、C-D

其过程貌似是一样的,但是算法一的复杂就在于没有充分利用前面已经求出的子序列和的值。

举个例子,算法一在求A-D连续子序列和的值时,其过程为A-D = A-B + B-C + C-D;

而对于算法二,A-D连续子序列和的求值过程为A-D = A-C+C-D;

这样,算法二充分利用了前面的计算值,这样就大大减少了计算子序列和的步骤。

算法三:递归法(分治法)

时间复杂度:O(NlogN)

易知,对于一数字序列,其最大连续子序列和对应的子序列可能出现在三个地方。或是整个出现在输入数据的前半部(左),或是整个出现在输入数据的后半部(右),或是跨越输入数据的中部从而占据左右两半部分。前两种情况可以通过递归求解,第三种情况可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而得到,然后将这两个和加在一起即可。

其实现代码为:

int MaxSubSequence(const int A[],int N)
{
	return MaxSubSum(A,0,N-1);
}

static int MaxSubSum(const int A[], int Left, int Right)
{
	int MaxLeftSum,MaxRightSum;
	int MaxLeftBorderSum,MaxRightBorderSum;
	int LeftBorderSum,RightBorderSum;
	int Center,i;

	if(Left == Right)
	{
		if(A[Left] > 0)
			return A[Left];
		else
			return 0;
	}

	Center = (Left + Right)/2;
	MaxLeftSum = MaxSubSequence(A,Left,Center);
	MaxRightSum = MaxSubSequence(A,Center+1,Right);

	MaxLeftBorderSum = 0;
	LeftBorderSum = 0;
	for(i = Center;i >= Left;i--)
	{
		LeftBorderSum += A[i];
		if(LeftBorderSum > MaxLeftBorderSum)
			MaxLeftBorderSum = LeftBorderSum;
	}

	MaxRightBorderSum = 0;
	RightBorderSum = 0;
	for(i = Center+1;i <= Right;i++)
	{
		RightBorderSum += A[i];
		if(RightBorderSum > MaxRightBorderSum)
			MaxRightBorderSum = RightBorderSum;
	}	

	return Max(MaxLeftSum,MaxRightSum,MaxLeftBorderSum + MaxRightBorderSum);
} 

int Max(int a, int b, int c)
{
	if(a>b&&a>c)
		return a;
	else if(b>a&&b>c)
		return b;
	else
		return c;
}

现在对上面的代码进行相关说明:

Center变量所确定的值将处理序列分割为两部分,一部分为Center前半部,一部分为Center+1后半部。

在上文,我们提到,最大连续子序列的出现位置有三种情况。

对于前两种情况,我们根据递归特性,可以得到:

	MaxLeftSum = MaxSubSequence(A,Left,Center);
	MaxRightSum = MaxSubSequence(A,Center+1,Right);

而对于第三种情况,我们需要先求出前半部包含最后一个元素的最大子序列:

	MaxLeftBorderSum = 0;
	LeftBorderSum = 0;
	for(i = Center;i >= Left;i--)
	{
		LeftBorderSum += A[i];
		if(LeftBorderSum > MaxLeftBorderSum)
			MaxLeftBorderSum = LeftBorderSum;
	}

然后,再求出后半部包含第一个元素的最大子序列:

	MaxRightBorderSum = 0;
	RightBorderSum = 0;
	for(i = Center+1;i <= Right;i++)
	{
		RightBorderSum += A[i];
		if(RightBorderSum > MaxRightBorderSum)
			MaxRightBorderSum = RightBorderSum;
	}	

最后,我们只需比较这三种情况所求出的最大连续子序列和,取最大的一个,即可得到需要求解的答案。

return Max(MaxLeftSum,MaxRightSum,MaxLeftBorderSum + MaxRightBorderSum);

我们在介绍这个算法的开始,就已经提到了其时间复杂度,现在做一个推导:

令T(N)是求解大小为N的最大连续子序列和问题所花费的时间。

当N==1时,T(1) = 1;

当N>1时,T(N) = T(N/2) + O(N);

有数学推导公式,我们可以得到:

     T(N) = NlogN + N =O(NlogN)。

算法四:动态规划法

时间复杂度:O(N)

终于到了动态规划的部分了,这么一步一步走来,感受到了算法的无穷魅力。那么如何用动态规划来处理这个问题?

首先,我们重温将一个问题用动态规划方法处理的准则:

“最优子结构”、“子问题重叠”、“边界”和“子问题独立”。

在本问题中,我们可以将子序列与其子子序列进行问题分割。

最后得到的状态转移方程为:

      
MaxSum[i] = Max{ MaxSum[i-1] + A[i], A[i]};

在这里,我们不必设置数组MaxSum[]。

代码实现:

int MaxSubSequence(const int A[], int N)
{
	int ThisSum,MaxSum,j;
	ThisSum = MaxSum =0;
	for(j = 0;j < N;j++)
	{
		ThisSum += A[j];

		if(ThisSum > MaxSum)
			MaxSum = ThisSum;
		else if(ThisSum < 0)
			ThisSum = 0;
	}
	return MaxSum;
}

在本代码实现中,ThisSum持续更新,同时整个过程,只对数据进行了一次扫描,一旦A[i]被读入处理,它就不再需要被记忆。(联机算法)

小结:

整个过程是一个思想的选择问题,从最初的穷举法,到分治法,再到动态规划法。算法设计思想的灵活选择是处理一个实际问题的关键。

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

时间: 2024-10-12 17:20:11

动态规划经典题目:最大连续子序列和的相关文章

[leetcode]53Maximum Subarray动态规划经典题目:最大子串问题

/** * Find the contiguous subarray within an array (containing at least one number) * which has the largest sum. For example, given the array [-2,1,-3,4,-1,2,1,-5,4], the contiguous subarray [4,-1,2,1] has the largest sum = 6. click to show more prac

最大连续子序列和-动态规划

题目描述: 给定K个整数的序列{ N1, N2, -, NK },其任意连续子序列可表示为{ Ni, Ni+1, -, Nj },其中 1 <= i <= j <= K.最大连续子序列是所有连续子序中元素和最大的一个, 例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20. 注意: 最大连续子序列和如果为负,则返回0:而本题目中的最大连续子序列和并不返回0,如果是全为负数,则返回最大的负数即可. 思路分析: -具有

HDU-1231-最大连续子序列(Java+DP动态规划)

最大连续子序列 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 21101    Accepted Submission(s): 9361 Problem Description 给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j

nyist oj 214 单调递增子序列(二) (动态规划经典)

单调递增子序列(二) 时间限制:1000 ms  |  内存限制:65535 KB 难度:4 描述 给定一整型数列{a1,a2...,an}(0<n<=100000),找出单调递增最长子序列,并求出其长度. 如:1 9 10 5 11 2 13的最长单调递增子序列是1 9 10 11 13,长度为5. 输入 有多组测试数据(<=7) 每组测试数据的第一行是一个整数n表示序列中共有n个整数,随后的下一行里有n个整数,表示数列中的所有元素.每个整形数中间用空格间隔开(0<n<=1

动态规划:最大连续子序列乘积

题目描述: 给定一个浮点数序列(可能有正数.0和负数),求出一个最大的连续子序列乘积. 分析:若暴力求解,需要O(n^3)时间,太低效,故使用动态规划. 设data[i]:第i个数据,dp[i]:以第i个数结尾的连续子序列最大乘积, 若题目要求的是最大连续子序列和,则易确定状态转移方程为: dp[i]=max(data[i],dp[i-1]+data[i])(dp[i]为以第i个数结尾的连续子序列最大和) 但乘积存在负负得正的问题,即原本很小的负数成了一个负数反而变大了,(负数逆袭了), 故不能

动态规划(Dynamic Programming)LeetCode经典题目

动态规划(DP)概述: 动态规划是运筹学的一个分支.(运筹学,是现代管理学的一门重要专业基础课.该学科利用统计学.数学模型和算法等方法,去寻找复杂问题中的最佳或近似最佳的解答.) 以局部最优解最终求得全局最优解.在设计动态规划算法时,需要确认原问题与子问题.动态规划状态.边界状态结值.状态转移方程等关键要素. 在算法面试中,动态规划是最常考察的题型之一,大多数面试官都以是否可较好地解决动态规划相关问题来区分候选者是否“聪明”. 下面就让我们开始8道经典的动态规划相关题目吧!! 1.LeetCod

动态规划:最大连续子序列和

问题:给出一个数组,求其连续子序列的最大和 package 动态规划; /** * 给出一个数组,求其连续子数组的最大和 * @author Administrator * */ public class MaxSum { public static void main(String[] args) { int[] arr = new int[]{-3,1,-3,4,-1,2,1}; int max=arr[0]; int current=arr[0]; for(int i=1;i<arr.le

题目1011:最大连续子序列(需要标记边界位置)

题目链接:http://ac.jobdu.com/problem.php?pid=1011 详解链接:https://github.com/zpfbuaa/JobduInCPlusPlus 参考代码: / // 1011 最大连续子序列.cpp // Jobdu // // Created by PengFei_Zheng on 28/04/2017. // Copyright © 2017 PengFei_Zheng. All rights reserved. // #include <std

九度-题目1011:最大连续子序列

题目描述:     给定K个整数的序列{ N1, N2, ..., NK },其任意连续子序列可表示为{ Ni, Ni+1, ..., Nj },其中 1 <= i <= j <= K.最大连续子序列是所有连续子序列中元素和最大的一个,例如给定序列{ -2, 11, -4, 13, -5, -2 },其最大连续子序列为{ 11, -4, 13 },最大和为20.现在增加一个要求,即还需要输出该子序列的第一个和最后一个元素. 输入: 测试输入包含若干测试用例,每个测试用例占2行,第1行给出