连续子序列最大和的O(NlogN)算法
对于一个数组,例如:
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)算法的更多相关文章
- 【ToReadList】六种姿势拿下连续子序列最大和问题,附伪代码(以HDU 1003 1231为例)(转载)
问题描述: 连续子序列最大和,其实就是求一个序列中连续的子序列中元素和最大的那个. 比如例如给定序列: { -2, 11, -4, 13, -5, -2 } 其最大连续子序列为{ 11, ...
- HDU-1231 简单dp,连续子序列最大和,水
1.HDU-1231 2.链接:http://acm.hdu.edu.cn/showproblem.php?pid=1231 3.总结:水 题意:连续子序列最大和 #include<iostre ...
- 动态规划:最大连续子序列乘积 分类: c/c++ 算法 2014-09-30 17:03 656人阅读 评论(0) 收藏
题目描述: 给定一个浮点数序列(可能有正数.0和负数),求出一个最大的连续子序列乘积. 分析:若暴力求解,需要O(n^3)时间,太低效,故使用动态规划. 设data[i]:第i个数据,dp[i]:以第 ...
- 最长上升子序列(LIS)长度的O(nlogn)算法
最长上升子序列(LIS)的典型变形,熟悉的n^2的动归会超时.LIS问题可以优化为nlogn的算法.定义d[k]:长度为k的上升子序列的最末元素,若有多个长度为k的上升子序列,则记录最小的那个最末元素 ...
- codevs2622数字序列( 连续子序列最大和O(n)算法)
/* 算法描述:维护一个s[p]表示累加和 并且更新最大值ans 如果s[p]<0 则从p+1重新累加 证明:设某个区间的起点和终点分别为s t 分两种情况 1.t<p:设s2表示1到s的 ...
- HDU 4223 Dynamic Programming?(最小连续子序列和的绝对值O(NlogN))
传送门 Description Dynamic Programming, short for DP, is the favorite of iSea. It is a method for solvi ...
- K:求取数组中最大连续子序列和的四个算法
相关介绍: 求取数组中最大连续子序列和问题,是一个较为"古老"的一个问题.该问题的描述为,给定一个整型数组(当然浮点型也是可以的啦),求取其下标连续的子序列,且其和为该数组的所有 ...
- 最长上升子序列O(nlogn)算法详解
最长上升子序列 时间限制: 10 Sec 内存限制:128 MB 题目描述 给定一个序列,初始为空.现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置.我们想知道此时最长上升子 ...
- 最长不下降子序列的O(n^2)算法和O(nlogn)算法
一.简单的O(n^2)的算法 很容易想到用动态规划做.设lis[]用于保存第1~i元素元素中最长不下降序列的长度,则lis[i]=max(lis[j])+1,且num[i]>num[j],i&g ...
随机推荐
- node.js零基础详细教程(7):node.js操作mongodb,及操作方法的封装
第七章 建议学习时间4小时 课程共10章 学习方式:详细阅读,并手动实现相关代码 学习目标:此教程将教会大家 安装Node.搭建服务器.express.mysql.mongodb.编写后台业务逻辑. ...
- js替换字符串中所有指定的字符
第一次发现JavaScript中replace() 方法如果直接用str.replace("-","!") 只会替换第一个匹配的字符. 而str.replac ...
- Jenkin-持续集成
1.Jenkins安装 本文将会介绍如何在windows 中安装Jenkins,并且使用Jenkins进行项目的构建. 首先我们进入到Jenkins 的官网下载地址:https://jenkins.i ...
- drozer使用
1.启用adb 端口转发 adb forward tcp:314154 tcp:31415 2.启用drozer 3.链接drozer drozer console connect 4:如果没 ...
- php中的捕获异常操作
<?php if(!isset($_SESSION)){ session_start(); } include '../common/mysql.class.php'; include '../ ...
- 大话Session
[原创]转载请保留出处:shoru.cnblogs.com 晋哥哥的私房钱 引言 在web开发中,session是个非常重要的概念.在许多动态网站的开发者看来,session就是一个变量,而且其表现像 ...
- 如何解决java文件上面有错,但是文件夹上面不显示的错误
查了网上很多办法,都是解决不了的,不是我说什么,有些人真的人云亦云,没解决的瞎**乱转发 解决方法是:换个工作空间(workplace)即可,不行你打我
- Mac应用推荐
知识管理 Outline Curio Together 开发 Clion Vim + spf13 Transmit 辅助应用 Moom PopClip Timing AppClean Markdown ...
- MAC开发环境安装
MAC开发环境安装 安装sancha cmd: 安装: https://www.sencha.com/products/extjs/cmd-download/ cmd运行 $ open .bash_p ...
- 2017寒假零基础学习Python系列之函数之 递归函数
什么是递归函数? 在函数内部,也可以继续调用其他函数,如果一个函数在内部调用本身,这个函数为递归函数举一个求n的阶乘的例子: def fact(n): if n == 1: return 1; els ...