对于一个数组,例如:
int[] a = {4,-3,5,-2,-1,2,6,-2}
找出一个连续子序列,对于任意的i和j,使得a[i]+a[i+1]+a[i+2]+.......+a[j]他的和是所有子序列中最大的,这个连续子序列
被称为和最大的连续子序列,上面那个例子的连续子序列最大和应该是11,
由4 + -3 + 5 + -2 + -1 + 2 + 6 = 11得出,但是如果我们用程序
表示应该如何进行又快又好地计算呢?
最近正在看《数据结构和问题求解》这本书,书上介绍了一个分治算法(至少含有两个递归的算法叫分治),
看的我晕晕乎乎的,所以在这里写一下我对这个O(NlogN)算法的理解,加深一下理解程度= =

计算一个序列的连续子序列最大和
如:int[] a = {4,-3,5,-2,-1,2,6,-2}
考虑将这个序列划分成两个部分
即 4,-3,5,-2 ||| -1,2,6,-2
有三种情况
1.这个和最大的连续子序列出现在这个序列的左边
2.这个和最大的连续子序列出现在这个序列的右边
3.这个和最大的连续子序列横跨左右,是从左边某一个地方到右边某一个地方

我们先来考虑第3种情况,和最大的连续子序列是在中间的情况,
首先,如果连续子序列在中间的话,以下面这个序列为例:
4,-3,5,-2 ||| -1,2,6,-2
那-2和-1是肯定在这个连续子序列里的,因为要横跨分开来的左右两个子序列,
必然包括左边序列的最后一个元素和右边序列的最后一个元素,有了这个认识之后
后面的要做的事就变得简单了,我们可以先找①左边这个序列包括-2的和最大子序列,
再找②右边这个序列包括-1的和最大子序列,那么这个横跨左右序列的和最大子序列
的必然是由前面那两个子序列①②相加而来。
再①②上找最大子序列的操作实质是一个复杂度为O(N)的操作,即
从-2开始,往前面扫,到5的时候,和是3,到-3的时候和是0,到4的时候和是4,由此确定4是
右边这个序列的最大的和,-2 + 5 + -3 + 4则是包含-2的和最大连续子序列,以下写出在运算过程
的值
序列: 4 , -3 , 5 , -2 ||| -1 , 2 , 6 , -2
运算过程的值: *4 0 3 -2 -1 1 *7 5
带星号的是在当前分到的序列中最大的和
所以最后答案就是4+7 = 11
有此我们可以知道第三种情况是可以在O(N)时间内解决的

接着我们看第一和第二种情况,即和最大的连续子序列在左边或者在右边的情况,此时如果我们
对这种情况依然进行穷举的O(N2)的n方算法的话,实际上最后时间也没有减少多少,所以我们要
想到更好的办法来对付这个第一和第二种情况。首先,想想看既然第三种情况时间复杂度这么短(O(N))
要是每次操作都是第三种情况,不是最好么,那么有没有可能把第一种和第二种情况变成第三种情况呢,
那要是把第一种和第二种情况下的序列再分成两半直到分到不能再分为止呢?也就是,递归的解决情况1和
情况2,使情况1和2变成了更小的问题,简单来说
步骤@1
一个序列:
4,-3,5,-2,-1,2,6,-2
先把他分成两半
4 , -3 , 5 , -2 ||| -1 , 2 , 6 , -2
这个时候,我们的目标从最初的计算整个序列(4,-3,5,-2,-1,2,6,-2)的连续子序列最大和转化成了计算下面三个里面最大的那个
即max(①,②,③)
①计算左边(4 , -3 , 5 , -2)的序列的最大和和
②右边的(-1 , 2 , 6 , -2)序列的最大和
③中间(即肯定包含-2和-1的连续子序列)的(4 , -3 , 5 , -2 ||| -1 , 2 , 6 , -2)序列的最大和,
中间序列由我们上面的方法可以算出来,这个时候我们再单独看左边这个序列
步骤@2
一个序列:
4 , -3 , 5 , -2
先把他分成两半
4 , -3 ||| 5 , -2
是的,这个新的序列(4 , -3 , 5 , -2),我们的目标要计算他的所有可能的连续子序列的最大和,那么,我们的目标又可以
进一步的转化成求max(①,②,③)
①计算左边(4 , -3)的序列的最大和和
②右边的(5 , -2)序列的最大和
③中间(即肯定包含-3和5的连续子序列)的(4 , -3 ||| 5 , -2)序列的最大和,
再单独看左边这个序列,嗯?是不是有一种很熟悉的感觉,是的,依然是
步骤@3
一个序列:
4 , -3
先把他分成两半
4 ||| -3
现在的情况是基线情况,也就是,把他分到不能再分了,那么,左边的连续子序列的最大和不是一目了然么
(就是4啦),右边的连续子序列最大和是0(注意如果一个序列全是负数,那他的连续子序列和是0,也就是
什么也不选的情况)
这个时候知道了步骤@2的左边序列的最大和(也就是4),也知道了中间序列的最大和(依我们前面提到的方法),我们再
看步骤@2的右边序列....那么,又开始一波熟练的操作了:
步骤@4
一个序列:
5 , -2
先把他分成两半
5 ||| -2
可以知道步骤@2的右边序列的最大和是5,那么现在让我们回到步骤@2,步骤@2的中间序列的最大和是6,这个时候终于可以知道
步骤@2的那个一整个序列的连续子序列最大和是多少啦,也就是5,6,4三个里的最大值,那么当然是6啦,然后我们再手算验证一下
步骤@2的序列(4 , -3 , 5 , -2)
4 + -3 + 5 = 6
的确是这个序列所有可能的连续子序列里的是最大和的子序列,这个时候我们已经知道了步骤@1(也就是我们的目标序列)的左边的序列的
最大和了,哦,当然,还有我们中间序列的最大和,距离我们的目标已经完成了三分之二了>.< , 接着就是按照刚刚那样同样的方法
进行右边序列的最大和的递归计算,至此,我们达成了前面提到的那三个目标(①,②,③)
①计算左边(4 , -3 , 5 , -2)的序列的最大和和
②右边的(-1 , 2 , 6 , -2)序列的最大和
③中间(即肯定包含-2和-1的连续子序列)的(4 , -3 , 5 , -2 ||| -1 , 2 , 6 , -2)序列的最大和
那么求这个最终序列(4 , -3 , 5 , -2,-1 , 2 , 6 , -2)的连续子序列最大和就可以顺利完成了~~~~~~
以下是我的java代码:

错误版本:

     public static int calculate_continuous_sequence(int[] a,int left,int right){
//在左边的序列的连续子序列最大和
int maxLeftSum_continuous_sequence = 0;
//在右边的序列的连续子序列最大和
int maxRightSum_continuous_sequence = 0;
//在中间的序列的连续子序列最大和
int maxMidSum_continuous_sequence = 0; //基线情况,变成一个元素的情况,那么
//这个元素就是她自己的连续子序列的最大和(负数是0)
if(left==right){
if(a[left]<0){
return 0;
}
return a[left];
} int mid = (left+right)/2;
//计算左边的连续子序列和的最大值
maxLeftSum_continuous_sequence = calculate_continuous_sequence(a,left,mid);
//计算右边的连续子序列最大值
maxRightSum_continuous_sequence = calculate_continuous_sequence(a,mid+1,right); int mid_left_Sum = 0; //计算中间的时候左边的最大和
int mid_right_Sum = 0; //计算中间的时候右边的最大和
int result = 0;
//接着计算中间的连续子序列最大值
for(int i=mid;i>=0;i--){
result += a[i];
if(result>mid_left_Sum){
mid_left_Sum = result;
}
}
result = 0;
for(int i=mid+1;i<=a.length-1;i++){
result += a[i];
if(result>mid_right_Sum){
mid_right_Sum = result;
}
} maxMidSum_continuous_sequence = mid_left_Sum+mid_right_Sum; return max3(maxLeftSum_continuous_sequence,maxRightSum_continuous_sequence,maxMidSum_continuous_sequence);
}
public static int max3(int i,int j,int k){
int a = Math.max(i, j);
return Math.max(a,k);
}

果然我还是太渣渣,以为自己还算熟练了...结果一写就犯了小毛病= =,在上面我的错误代码里面,计算连续子序列最大和在中间的情况错了
计算中间的情况应该是从每一个序列的左边出发到中间,从中间出发到右边,然后两两相加,结果我写成了从a序列的左边到中间,从a序列
的中间到右边,而不是每次递归下去变成小问题的新序列..所以就错的离谱啦~~~还有另一个错误,问题依然在计算中间的那段代码中~~~
下面是正确版本:

     /**
* @param a : 要求的序列
* @param left : 从要求的序列的哪里开始
* @param right : 从要求的序列哪里结束
* @return 返回序列a的ai~aj范围内连续子序列最大和
*/
public static int calculate_continuous_sequence(int[] a,int left,int right){
//在左边的序列的连续子序列最大和
int maxLeftSum_continuous_sequence = 0;
//在右边的序列的连续子序列最大和
int maxRightSum_continuous_sequence = 0;
//在中间的序列的连续子序列最大和
int maxMidSum_continuous_sequence = 0; //基线情况,变成一个元素的情况,那么
//这个元素就是她自己的连续子序列的最大和(负数是0)
if(left==right){
if(a[left]<0){
return 0;
}
return a[left];
} int mid = (left+right)/2;
//计算左边的连续子序列和的最大值
maxLeftSum_continuous_sequence = calculate_continuous_sequence(a,left,mid);
//计算右边的连续子序列最大值
maxRightSum_continuous_sequence = calculate_continuous_sequence(a,mid+1,right); int mid_left_Sum = 0; //计算中间的时候左边的最大和
int mid_right_Sum = 0; //计算中间的时候右边的最大和
int result = 0;
//接着计算中间的连续子序列最大值
for(int i=mid;i>=left;i--){
result += a[i];
if(result>mid_left_Sum){
mid_left_Sum = result;
}
}
result = 0;
for(int i=mid+1;i<=right;i++){
result += a[i];
if(result>mid_right_Sum){
mid_right_Sum = result;
}
} maxMidSum_continuous_sequence = mid_left_Sum+mid_right_Sum; return max3(maxLeftSum_continuous_sequence,maxRightSum_continuous_sequence,maxMidSum_continuous_sequence);
}
public static int max3(int i,int j,int k){
int a = Math.max(i, j);
return Math.max(a,k);
}

因为本人只是个刚开始学算法的渣渣,如果有什么错误,希望大家可以指出

连续子序列最大和的O(NlogN)算法的更多相关文章

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

    问题描述:       连续子序列最大和,其实就是求一个序列中连续的子序列中元素和最大的那个. 比如例如给定序列: { -2, 11, -4, 13, -5, -2 } 其最大连续子序列为{ 11, ...

  2. HDU-1231 简单dp,连续子序列最大和,水

    1.HDU-1231 2.链接:http://acm.hdu.edu.cn/showproblem.php?pid=1231 3.总结:水 题意:连续子序列最大和 #include<iostre ...

  3. 动态规划:最大连续子序列乘积 分类: c/c++ 算法 2014-09-30 17:03 656人阅读 评论(0) 收藏

    题目描述: 给定一个浮点数序列(可能有正数.0和负数),求出一个最大的连续子序列乘积. 分析:若暴力求解,需要O(n^3)时间,太低效,故使用动态规划. 设data[i]:第i个数据,dp[i]:以第 ...

  4. 最长上升子序列(LIS)长度的O(nlogn)算法

    最长上升子序列(LIS)的典型变形,熟悉的n^2的动归会超时.LIS问题可以优化为nlogn的算法.定义d[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素 ...

  5. codevs2622数字序列( 连续子序列最大和O(n)算法)

    /* 算法描述:维护一个s[p]表示累加和 并且更新最大值ans 如果s[p]<0 则从p+1重新累加 证明:设某个区间的起点和终点分别为s t 分两种情况 1.t<p:设s2表示1到s的 ...

  6. HDU 4223 Dynamic Programming?(最小连续子序列和的绝对值O(NlogN))

    传送门 Description Dynamic Programming, short for DP, is the favorite of iSea. It is a method for solvi ...

  7. K:求取数组中最大连续子序列和的四个算法

    相关介绍:  求取数组中最大连续子序列和问题,是一个较为"古老"的一个问题.该问题的描述为,给定一个整型数组(当然浮点型也是可以的啦),求取其下标连续的子序列,且其和为该数组的所有 ...

  8. 最长上升子序列O(nlogn)算法详解

    最长上升子序列 时间限制: 10 Sec   内存限制:128 MB 题目描述 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.我们想知道此时最长上升子 ...

  9. 最长不下降子序列的O(n^2)算法和O(nlogn)算法

    一.简单的O(n^2)的算法 很容易想到用动态规划做.设lis[]用于保存第1~i元素元素中最长不下降序列的长度,则lis[i]=max(lis[j])+1,且num[i]>num[j],i&g ...

随机推荐

  1. 一个比较完善的httpWebRequest 封装,适合网络爬取及暴力破解

    大家在模拟http请求的时候,对保持长连接及cookies,http头部信息等了解的不是那么深入.在各种网络请求过程中,发送N种问题. 可能问题如下: 1)登录成功后session保持 2)保证所有c ...

  2. 中学之Vim实践课程

    今天转发娄老师的一篇VIM编辑器的文章,很赞哦!(值得收藏)文后的参考资料记得看一看,也很棒!                               原文地址:http://www.cnblog ...

  3. php导入csv文件

    <?php /** * Created by PhpStorm. * User: hanks * Date: 2017/4/30 * Time: 13:24 */ include 'header ...

  4. 跨进程通信之Messenger

    1.简介 Messenger,顾名思义即为信使,通过它可以在不同进程中传递Message对象,通过在Message中放入我们需要的入局,就可以轻松实现数据的跨进程传递了.Messenger是一种轻量级 ...

  5. python迭代器生成器(一)

    for循环可以用于python中任何序列类型,包括序列.元组以及字符串.例如: >>> for x in [1,2,3,4]: print(x * 2,end='')...2468 ...

  6. MySQL数据库Raid存储方案

    作为一名DBA,选择自己的数据存储在什么上面,应该是最基本的事情了.但是很多DBA却容易忽略了这一点,我就是其中一个.之前对raid了解的并不多,本文就记录下学习的raid相关知识. 一.RAID的基 ...

  7. centos 7 yum方式安装MySQL 5.6

    本文根据mysql的官方文档操作:https://dev.mysql.com/doc/mysql-yum-repo-quick-guide/en/ 由于Centos7 默认数据库是mariabd(网上 ...

  8. ABP入门系列(19)——使用领域事件

    ABP入门系列目录--学习Abp框架之实操演练 源码路径:Github-LearningMpaAbp 1.引言 最近刚学习了下DDD中领域事件的理论知识,总的来说领域事件主要有两个作用,一是解耦,二是 ...

  9. 基于Bootstrap+angular的一个豆瓣电影app

    1.搭建项目框架 npm初始化项目 npm init -y //按默认配置初始化项目 安装需要的第三方库 npm install bootstrap angular angular-route --s ...

  10. 一步一步学Vue (一)

    vue应该是前端主流框架中的集成大成者,它吸取了knockout,angular,react设置avalon的经验,支持各种模式写法,入门很简单,从本章开始,会记录学习vue中的点点滴滴,以笔记的形式 ...