连续子序列最大和问题的四种经典解答

问题描述

给定(可能是负的)整数序列A1, A2,...,AN, 寻找(并标识)使Sum(Ak)(k >=i, k <= j)的值最大的序列。如果所有的整数都是负的,那么连续子序列的最大和是那个最大的负数项。最好给出给出最大和连续子序列!!

1 暴力破解法

这个问题有一个最简单直接的穷举解决法。我们看问题,既然要求里面最大的连续子序列。那么所有的连续子序列将由哪些组成呢?以数组的第一个元素为例,连续子序列必须是至少包含元素A1,也可能包含从A1到A2...以及从A1到AN。这样就有N种可能。后面的元素也按照这样类似的办法,以该元素开始,包含该元素的单元素数组,两个元素数组...直到包含数组末尾的数组。

#include <iostream>
#include <cstdlib>
#include <vector>

using namespace std;

int main()
{
	vector<int> array;
	for(int i=0;i<20;i++)
	{
		int j = 50-random()%100; //-49~50
		array.push_back(j);
	}

	for(vector<int>::iterator it=array.begin();it!=array.end();++it)
	{
		cout<<*it<<" ";
	}
	cout<<endl;
	int maxsum=-50; //注意初始化
	int low=0;
       int high=0;
	for(int i=0;i<array.size();i++)
	{
		int sum=0;

		for(int j=i;j<array.size();j++)
		{
			sum+=array[j];
			if(sum>maxsum)
			{
				maxsum=sum;
				low=i;
				high=j;
			}
		}
	}
	cout<<"subarray:"<<array[low]<<"~"<<array[high]<<endl;
	cout<<"maxsun="<<maxsum<<endl;
return 0;
}

结果:

时间复杂度分析:两重循环遍历,复杂度为O(n^2)

2 分治策略(递归法)

对这个问题,有一个相对复杂的O(NlogN)的解法,就是使用递归。如果要是求出序列的位置的话,这将是最好的算法了(因为我们后面还会有个O(N)的算法,但是不能求出最大子序列的位置)。该方法我们采用“分治策略”(divide-and-conquer)。

在我们例子中,最大子序列可能在三个地方出现,或者在左半部,或者在右半部,或者跨越输入数据的中部而占据左右两部分。前两种情况递归求解,第三种情况的最大和可以通过求出前半部分最大和(包含前半部分最后一个元素)以及后半部分最大和(包含后半部分的第一个元素)相加而得到。

#include <iostream>
#include <cstdlib>
#include <vector>

using namespace std;
/*
* find a subarray crossing the mid with the biggist sum
*/

int find_max_crossing_subarray(vector<int> array,int low, int mid, int high)
{
    int left_maxsum=-50; //for sum
    int right_maxsum=-50; //for sum
    int sum =0;
    for(int i= mid;i>= low;--i)
    {
        sum += array[i];
        if(sum > left_maxsum)
        {
            left_maxsum=sum;

        }
    }

    sum=0;
    for(int i=mid+1;i<=high;++i)
    {
        sum += array[i];
        if(sum > right_maxsum)
        {
            right_maxsum=sum;

        }
    }
return(left_maxsum+right_maxsum);

//cout<<"maxsum_crossing_mid"<<left_maxsum+right_maxsum<<endl;
}

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;
}
/*
* find a subarray on the left or right side of mid
*/
int find_max_subarray(vector<int> array, int low, int high)
{

    if(low==high)
    {

	return array[low];
    }
    int mid = int((low+high)/2);
    int low_maxsum = find_max_subarray(array, low, mid);
    int high_maxsum = find_max_subarray(array, mid+1, high);
    int crossing_mid_maxsum = find_max_crossing_subarray(array, low, mid, high);
    return max(low_maxsum, high_maxsum, crossing_mid_maxsum);
}
int main()
{
    vector<int> array;
    for(int i=0;i<10;i++)
    {
        int j = 50-rand()%100; //-49~50
        array.push_back(j);
    }

    for(vector<int>::iterator it=array.begin();it!=array.end();++it)
    {
        cout<<*it<<" ";
    }
cout<<endl;
int maxsum =find_max_subarray(array, 0, array.size()-1);
cout<<"the maxsum="<<maxsum<<endl;
//cout<<"range from"<<array[a[1]]<<"to"<<array[a[2]]<<endl;
return 0;
}

结果:

3 增量算法(插入算法)

假设已知A[1~N]的最大顺序子序列和,那么对于A[1~N+1]序列,增加一个项,最大顺序子序列和会在哪里产生呢?(1)不变,A[N+1]项不影响(2)在包括A[N+1]项往前的子序列中产生。

#include <iostream>
#include <cstdlib>
#include <vector>

using namespace std;

int enlarge(vector<int> array,int maxsum, int i)
{
	if(i>array.size() || i<=0)
		return -1;
	int sum=0;
	for(int j=i;j>=0;--j)
		sum += array[j];
	if(sum>maxsum)
	maxsum=sum;
	return maxsum;
}
int find_maxsum(vector<int> array)
{
	if(array.empty())
		return -1;
	int maxsum=array.at(0);
	for(int i=1;i<=array.size()-1;++i)
	{
		int result = enlarge(array, maxsum, i);
		if(result>maxsum && result != -1)
			maxsum = result;
	}
	return maxsum;
}
int main()
{
	vector<int> array;
	for(int i=0;i<10;++i)
		array.push_back(10-rand()%20);
	for(vector<int>::iterator it= array.begin();it != array.end();++it)
		cout<<*it<<" ";
	cout<<endl;
	int max = find_maxsum(array);
	cout<<"maxsum ="<<max<<endl;
	return 0;
}

结果:

复杂度 O(n^2)

4 最优解(线性复杂度 O(N))

参考:http://www.cnblogs.com/CCBB/archive/2009/04/25/1443455.html

//线性的算法O(N)
long maxSubSum4(const vector<int>& a)
{
       long maxSum = 0, thisSum = 0;
       for (int j = 0; j < a.size(); j++)
       {
              thisSum += a[j];
              if (thisSum > maxSum)
                     maxSum = thisSum;
              else if (thisSum < 0)
                     thisSum = 0;
       }
       return maxSum;
}

很容易理解时间界O(N) 是正确的,但是要是弄明白为什么正确就比较费力了。其实这个是算法二的一个改进。分析的时候也是i代表当前序列的起点,j代表当前序列的终点。如果我们不需要知道最佳子序列的位置,那么i就可以优化掉。

重点的一个思想是:如果a[i]是负数那么它不可能代表最有序列的起点,因为任何包含a[i]的作为起点的子序列都可以通过用a[i+1]作为起点来改进。类似的有,任何的负的子序列不可能是最优子序列的前缀。例如说,循环中我们检测到从a[i]到a[j]的子序列是负数,那么我们就可以推进i。关键的结论是我们不仅可以把i推进到i+1,而且我们实际可以把它一直推进到j+1。

举例来说,令p是i+1到j之间的任何一个下标,由于前面假设了a[i]+…+a[j]是负数,则开始于下标p的任意子序列都不会大于在下标i并且包含从a[i]到a[p-1]的子序列对应的子序列(j是使得从下标i开始成为负数的第一个下标)。因此,把i推进到j+1是安全的,不会错过最优解。注意的是:虽然,如果有以a[j]结尾的某序列和是负数就表明了这个序列中的任何一个数不可能是与a[j]后面的数形成的最大子序列的开头,但是并不表明a[j]前面的某个序列就不是最大序列,也就是说不能确定最大子序列在a[j]前还是a[j]后,即最大子序列位置不能求出。但是能确保maxSum的值是当前最大的子序列和。这个算法还有一个有点就是,它只对数据进行一次扫描,一旦a[j]被读入处理就不需要再记忆。它是一个联机算法。

联机算法:在任意时刻算法都能够对它已读入的数据给出当前数据的解。

常量空间线性时间的联机算法几乎是完美的算法。

连续子序列最大和问题的四种经典解答

时间: 2024-10-08 18:17:03

连续子序列最大和问题的四种经典解答的相关文章

【ToReadList】六种姿势拿下连续子序列最大和问题,附伪代码(以HDU 1003 1231为例)(转载)

问题描述:       连续子序列最大和,其实就是求一个序列中连续的子序列中元素和最大的那个. 比如例如给定序列: { -2, 11, -4, 13, -5, -2 } 其最大连续子序列为{ 11, -4, 13 },最大和为20. =============================================================== 问题分析: 1.首先最朴素的方法是暴力 O(n^3) 直接两个for循环枚举子序列的首尾,然后再来个循环计算序列的和,每次更新和的最大值.

HDU1003 Max Sum 最大子序列和的问题【四种算法分析+实现】

就拿杭电OJ上的第1003题开始吧,这题比原书要复杂一些. Problem Description Given a sequence a[1],a[2],a[3]......a[n], your job is to calculate the max sum of a sub-sequence. For example, given (6,-1,5,4,-7), the max sum in this sequence is 6 + (-1) + 5 + 4 = 14. Input The fi

安卓企业开发(三) activity的四种经典传值方法

开发中遇到多个activity的传值问题 相邻两个之间的传值 或者多个三个以上之间的传值问题 但是很多同学这方面经验还是不足,说下常用的开发场景 1 一般的注册或者添加某项信息界面就会遇activity传值问题 2  比如我在一个界面提交新息  需要打开一个新的界面选择里面的信息回到当前activty的时候 现在说下比较经典的四种比较经典的传值方法 一 如果是两个相邻activity之间的传值: 可以用Intent传值 对象和单个属性都可以都可以 Intent intent =new Inten

连续子序列的最大乘积

问题描述 给定一个整数序列,序列中可能含有0,正数,负数,求出连续子序列乘积的最大值 暴力解法 双层循环,一一遍历,每次将当前结果与前次结果要比较,如果大于前次结果,更新最大值,时间复杂度为O(n2),复杂度较高,代码略 动态规划解法 对于序列arr,maxDp[k]表示以arr[k](必须包含arr[k])结尾的最大连续子序列乘积值,minDp[k]表示以arr[k](必须包含arr[k])结尾的最小连续子序列乘积值,则对于maxDp[k + 1],表示以arr[k + 1]结尾的最大连续子序

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

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

动态规划的简要总结和四个经典问题的c++实现

本文给出了动态规划的简要定义.适用场景.算法实现.并给出了四种经典动态规划:钢条切割求最大收益问题.矩阵链相乘求最小乘法次数问题.最长公共子序列问题.求最小的搜索代价的最优二叉搜索树的c++代码实现. 定义 性质 适用条件 算法实现过程 首先观察问题是否满足最优子结构性质 写出递归等式递归的定义子问题的最优解 求解子问题的最优解 构造最优解 四个经典问题的cpp实现 1 钢条切割 2 矩阵链相乘 3 最长公共子序列 4 最优二叉搜索树 代码下载 1. 定义 动态规划(dynamic progra

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

给定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^

[C++]四种方式求解最大子序列求和问题

问题 给定整数: A1,A2,-,An,求∑jk=iAk 的最大值(为方便起见,假设全部的整数均为负数,则最大子序列和为0) 比如 对于输入:-2,11,-4,13,-5,-2,答案为20,即从A2到A4 分析 这个问题之所以有意思.是由于存在非常多求解它的算法. 解法一:穷举遍历 老老实实的穷举出全部的可能,代码例如以下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 //计算并返回所

统计一个方阵中在四个方向长度为D的连续子序列的和

题目大意: 统计一个方阵中在四个方向长度为D的连续子序列的和 解题思路: 模拟 1 #include <bits/stdc++.h> 2 using namespace std; 3 4 const int imax_n = 505; 5 int a[imax_n][imax_n]; 6 int n, D; 7 8 void solve() 9 { 10 int ans = 0; 11 //hang 12 for (int i = 0; i < n; ++i) 13 { 14 int t