总和最大区间问题

题目和解题思路来源于吴军著作《计算之魂》。本题目是例题1.3。

文章目录

  • 1 问题描述
  • 2 解题思路
  • 2.1 三重循环
    • 2.2 两重循环
    • 2.3 分治法
    • 2.4 正反两遍扫描的方法
    • 2.5 再进一步,假设失效
  • 3 应用动态规划

1 问题描述

总和最大区间问题:给定一个实数序列,设计一个最有效的算法,找到一个总和最大的区间。
例如给定序列:1.5,-12.3,3.2,-5.5,23.2,3.2,-1.4,-12.2,34.2,5.4,-7.8,1.1,-4.9
总和最大的区间是从第5个数(23.2)到第10个数(5.4)。
这个问题的另外一种表述是:寻找一只股票最长的有效增长期。研究股票投资的人都想了解一只股票最长的有效增长期是哪一个时间段,即从哪一天买进到哪一天卖出,收益最大。上面这一组数字可以认为是一只股票每天的涨跌幅度(扣除大盘影响后)。

2 解题思路

2.1 三重循环

public int[] findMaxSumRange(double[] values){if(values == null || values.length==0) return null;int start = 0;int end = 0;double maxSum = Integer.MIN_VALUE;int K = values.length;for(int p=0;p<K; p++){//枚举起始位置for(int q=p;q<K;q++){//枚举终点位置//计算子数组的和double sum = 0;for(int i = p; i<=q; i++){sum += values[i];}if(maxSum < sum){maxSum = sum;start = p;end = q;}}}return new int[]{start, end};}

枚举起点p,范围是从0到K-1,枚举终点q,范围是从p到K-1。这些数字的综合为S(p,q)。
区间一头一尾的组合有O(K2)O(K^2)O(K2)种。计算S(p,q)平均要做K/4K/4K/4次加法。这又是一重循环。因此算法总复杂度为O(K3)O(K^3)O(K3)
关于:计算S(p,q)平均要做K/4K/4K/4次加法这是书中的描述。我自己计算过应该不是。例如K=10,会有55种组合,因此要计算55次。计算加法的次数是220次。所以每次计算平均是220/55=4220/55=4220/55=4,而K/4=10/4=2.5K/4=10/4=2.5K/4=10/4=2.5这是不一样的。但是从算法时间复杂度的角度是不受影响的。计算S(p,q)最少需要1次运算,最多需要K次。其平均值一定是一个关于K的一次函数。所以总体算法时间复杂度是O(K3)O(K^3)O(K3)

算法做了很多无用功。例如如果已经计算了S(0,3),在计算S(0,4)的时候只需要S(0,3)+values[4]即可。

2.2 两重循环

public int[] findMaxSumRangeV2(double[] values){if(values == null || values.length==0) return null;int start = 0;int end = 0;double maxSum = Integer.MIN_VALUE;int K = values.length;for(int p=0;p<K; p++){//枚举起始位置double sum = values[p];if(maxSum < sum){maxSum = sum;start = p;end = p;}for(int q=p+1;q<K;q++){//枚举终点位置//计算子数组的和sum += values[q];if(maxSum < sum){maxSum = sum;start = p;end = q;}}}return new int[]{start, end};}

实现方式和书中描述不完全一致。时间复杂度一致O(K2)O(K^2)O(K2)

2.3 分治法

1 首先将序列一分为二,分成从1到K/2,以及K/2+1到K两个子序列(下标从1开始的描述方式)
2 对这两个子序列分别求总和最大区间。
3 归并步骤。
如果前后2个子序列的综合最大区间中间没有间隔,也就是说前一个的总和最大区间是[p,K/2],后一个的总和最大区间恰好是[K/2+1,q]。在这种情况下,如果两个结果的和都是正数,那么整个序列总和最大区间是[p,q]。否则就取两个子序列的总和最大区间中的大的一个。
如果前后2个子序列的总和最大区间中间有间隔,我们假定这两个子序列的总和最大区间分别为[p1,q1]和[p2,q2]。那么这时候整个序列的总和最大区间是下面这三个中的一个:[p1,q1],[p2,q2],[p1,q2]。这一步的时间复杂度为O(K)。

总体算法时间复杂度为O(KlogK)O(KlogK)O(KlogK)
到此,你已经具备成为四级工程师的条件。因为你已经掌握了计算机科学的一个精髓:分治法。

public int[] findMaxSumRangeV3(double[] values){if(values == null || values.length==0) return null;int K = values.length;double[] result = findMaxSumRange(values, 0 , values.length-1);return new int[]{(int)result[0], (int)result[1]};}private double[] findMaxSumRange(double[] values, int startIndex, int endIndex) {if(startIndex == endIndex){return new double[]{startIndex, startIndex, values[startIndex]};}int middle = (startIndex + endIndex)/2;double[] result1 = findMaxSumRange(values, startIndex, middle);double[] result2 = findMaxSumRange(values,middle+1, endIndex);if(result1[1] == result2[0] + 1){if(result1[2]>0 && result2[2]>0){return new double[]{result1[0], result2[1], result1[2] + result2[2]};}if(result1[2]>result2[2]){return result1;}return result2;}else{double sum = 0;for(int i=(int)result1[0]; i<= (int)result2[1];i++){sum += values[i];}double[] max = result2;if(result1[2] > result2[2]){max = result1;}if(sum > max[2]){max = new double[]{result1[0], result2[1], sum};}return max;}}

2.4 正反两遍扫描的方法

正向扫描得到最大区间的右边界,反向扫描得到最大区间的左边界。具体做法如下。
1 先在序列中扫描找到第一个大于0的数。
1.1 假设整个数组都是负数或者0,那找到最大的数,也就是所要找的区间。
1.2 否则,从头部序列开始删除直到遇到第一个大于0的数。到此我们认为数组第0个元素是一个正数。

2 把左边界固定在第一个数,然后q=2,3,…K,计算S(1,q),以及到目前为止和最大值Maxf,和达到最大值的右边界r。

3 对于所有的q,都有S(1,q)>=0,或者存在某个q0q_0q0,当q>q0q>q_0q>q0的时候,符合,都有S(1,q)>=0。在这种情况下,当扫描到最后,即q=K时,所保留的那个Maxf所对应的r就是我们要找的区间的右边界。
为什么?因为从第r+1个数开始,或者是负数,或者是0,无论再怎么加,也不可能让和更大。

我们推论一下。 假设整个数组的最大区间和是S(l,r2)S(l,r_2)S(l,r2),并且r2>rr_2>rr2>r.
我们现在已知S(1,r)>S(1,r2)S(1,r)>S(1,r_2)S(1,r)>S(1,r2)
S(1,r2)=S(1,r)+S(r+1,r2)S(1,r_2)=S(1,r)+S(r+1,r_2)S(1,r2)=S(1,r)+S(r+1,r2) =>S(r+1,r2)<0=>S(r+1,r_2)<0=>S(r+1,r2)<0
S(l,r2)=S(l,r)+S(r+1,r2)S(l,r_2)=S(l,r)+S(r+1,r_2)S(l,r2)=S(l,r)+S(r+1,r2),因为S(r+1,r2)<0S(r+1,r_2)<0S(r+1,r2)<0,所以S(l,r2)<S(l,r)S(l,r_2)<S(l,r)S(l,r2)<S(l,r),这与假设S(l,r2)S(l,r_2)S(l,r2)是最大区间和矛盾,所以我们推出整个数组的最大区间和是S(l,r)S(l,r)S(l,r)。也就是说右边界确定是r。

可以看下表格中前向累计的结果。
在这里插入图片描述

从计算结果可以得知:Maxf=39.3,相应的r=10(下标从1开始)。

接下来只要把问题倒过来看。就可以知道左边界在哪里。我们从后往前计算累计之和。可以看出最大值Maxb=40.8,以及位置l=5。

问题的最终结果是:[5,10]。

public int[] findMaxSumRangeV4(double[] values){if(values == null || values.length==0) return null;double maxSum = Integer.MIN_VALUE;int K = values.length;int p = -1;for(int i=0;i<K;i++){if(values[i] > 0){p = i;break;}}if(p==-1){double max = Integer.MIN_VALUE;int maxOfIndex = -1;for(int i=0;i<K;i++){if(values[i] > max){max = values[i];maxOfIndex = i;}}return new int[]{maxOfIndex, maxOfIndex};}//从左到右double sum = values[p];double maxf = sum;int r = 0;for(int q=p + 1;q<K;q++){//计算子数组的和sum += values[q];if(maxf < sum){maxf = sum;r = q;}}//从右到左sum = values[K-1];maxf = sum;int l = K-1;for(int q = K -2; q >=0; q--){sum += values[q];if(maxf < sum){maxf = sum;l = q;}}return new int[]{l, r};}

2.5 再进一步,假设失效

2.4的解法的假设条件是:对于所有的q,都有S(1,q)>=0,或者存在某个q0q_0q0,当q>q0q>q_0q>q0的时候,符合,都有S(1,q)>=0。
如果在某个点之后S(1,q)都小于0会怎样?
把数组改为:1.5,-12.3,3.2,-5.5,23.2,3.2,-1.4,-62.2,44.2,5.4,-7.8,1.1,-4.9

在这里插入图片描述

如果还按照上面的算法,就会得出右边界r=6,左边界l=9。这是因为原本区间[9,10]是和最大区间。但是在累积了前面8个元素的之后后,和仍然小于0,我们就找不到它了。这样我们就需要改变一下。

我这里的疑问:在2.4中的推理过程应该没有用S(1,q)>=0这个条件。怎么就出错了呢?

1 我们先把左边界固定在第一个大于0的位置,例如p,然后让q=p,p+1,…K,计算S(p,q),以及目前为止最大的和Max和达到最大值的右边界r。如果我们计算到某一步q,发现S(p,q)<0,那么需要从q位置开始,反向计算Maxb,并且可以确定从p到q之间,最大区间和的区间,我们假定它为[l1,r1][l_1,r_1][l1,r1],区间和为Max1Max_1Max1

这里特别指出的是l1=pl_1=pl1=p。为什么呢?也可以用反证法证明。 我们假设如果l1≠pl_1 \ne pl1=p
根据我们对这种情况的假设:S(p,l1)>=0S(p,l_1)>=0S(p,l1)>=0,于是就有S(p,r1)=S(p,l1−1)+S(l1,r1)>=S(l1,r1)S(p,r_1)=S(p,l_1-1)+S(l_1,r_1)>=S(l_1,r_1)S(p,r1)=S(p,l11)+S(l1,r1)>=S(l1,r1),这就与[l1,r1][l_1,r_1][l1,r1]是到q为止和最大的区间相矛盾了。

2 我们从q+1继续开始扫描,重复步骤1。先是找到第一个大于0的元素,从那里开始做累加操作,可能遇到某个q′q'q,又出现了S(q+1,q′)<0S(q+1,q')<0S(q+1,q)<0的情况,这时候我们得到第二个局部和最大区间[l2,r2][l_2,r_2][l2,r2]。以及相应区间和Max2Max_2Max2

现在我们需要确定,从头开始到q’时的和最大的区间。我们只需要比较Max1Max_1Max1Max2Max_2Max2以及Max1+Max2+S(r1+1,l2−1)Max_1+Max_2+S(r_1+1,l_2-1)Max1+Max2+S(r1+1,l21).
Max1+Max2+S(r1+1,l2−1)Max_1+Max_2+S(r_1+1,l_2-1)Max1+Max2+S(r1+1,l21)也就是S(l1,r2)S(l_1,r_2)S(l1,r2)

在这里插入图片描述

我们可以先否定S(l1,r2)S(l_1,r_2)S(l1,r2)的可能性。

由于S(q+1,r2)=S(q+1,l2−1)+S(l2,r2)<S(l2,r2)S(q+1,r_2)=S(q+1,l_2-1)+S(l_2,r_2)<S(l_2,r_2)S(q+1,r2)=S(q+1,l21)+S(l2,r2)<S(l2,r2),所以S(q+1,l2−1)<0S(q+1,l_2-1)<0S(q+1,l21)<0,也就是说从第一次累加结束,到第二个局部和最大区间开始之间,所有的元素之和<0。

由于
S(p,q)<0S(p,q)<0S(p,q)<0,那么S(l1,r2)=S(p,r2)=S(p,q)+S(q+1,l2+1)+S(l2,r2)<S(l2,r2)=Max2S(l_1,r_2)=S(p,r_2)=S(p,q)+S(q+1,l_2+1)+S(l_2,r_2)<S(l_2,r_2)=Max_2S(l1,r2)=S(p,r2)=S(p,q)+S(q+1,l2+1)+S(l2,r2)<S(l2,r2)=Max2

这样一来,从序列头到q’时,和最大区间要么是[l1,r1][l_1,r_1][l1,r1],要么是[l2,r2][l_2,r_2][l2,r2]。那么只要取二者之中更大的那个保留为Max,[l,r]即可。

步骤3,采用步骤2的方法,继续向后扫描。得到一个个局部和最大的区间以及对应的部分和MaxiMax_iMaxi,然后比较MaxiMax_iMaxi和Max,做更新即可。

最后就得到了整个区间的总和最大和区间范围。
这应该就是线段树结构。待查证。

算法复杂度。无论是简单还是复杂情况,都只需要正向,反向各扫描一次数组。算法复杂度O(K)。

public int[] findMaxSumRangeV5(double[] values){if(values == null || values.length==0) return null;int K = values.length;//检查是否有大于0的元素int p = -1;for(int i=0;i<K;i++){if(values[i] > 0){p = i;break;}}if(p==-1){int maxOfIndex = argMax(values);return new int[]{maxOfIndex, maxOfIndex};}double maxSum = Integer.MIN_VALUE;//区间和最大值int l = -1, r = -1;double sum = 0;//某个区域内的累加和double maxF = Integer.MIN_VALUE;//某个区域内从左到右累加的和最大值int rF = p;//某个区域内和最大值的右边界int i = p;while(i < K){sum += values[i];if(sum<0){int q = i;//查找左边界double sumB = 0;double maxB = Integer.MIN_VALUE;int lF = q;for(int j=q;j>=p;j--){sumB += values[j];if(sumB > maxB){maxB = sumB;lF = j;}}//计算区域内的和double sumRange = rangeSum(values, lF, rF);if(sumRange > maxSum){maxSum = sumRange;l = lF;r = rF;}//查找下一个区段的起点while(q+1<K && values[q+1]<=0){q++;}p = q + 1;i = q + 1;}else if(maxF < sum){maxF = sum;rF = i;i++;}}return new int[]{l, r};}private int argMax(double[] values){double max = Integer.MIN_VALUE;int maxOfIndex = -1;for(int i=0;i<values.length;i++){if(values[i] > max){max = values[i];maxOfIndex = i;}}return maxOfIndex;}private double rangeSum(double[] values, int start, int end){double sumRange = 0;for(int j=start;j<=end;j++){sumRange += values[j];}return sumRange;}

3 应用动态规划

在第2部分,对于第4 和5部分的思考还是很复杂的。使用动态规划思想。我们用dp[i]表示以第i个元素为结尾的子数组的最大和。那么我们的答案就是max(dp)。
对于dp[i]来说,要么第i个元素作为前面子数组的最后一个元素,追加上去;要么就是单独成一个子数组。dp[i]=max(dp[i-1]+values[i], values[i]).

具体实现过程中使用了空间优化,只利用pre和maxSum即可。

public int[] findMaxSumRangeV6(double[] values){double pre = values[0];double maxSum = values[0];int l = 0, r = 0;int lF = 0, rF = 0;for(int i=0;i<values.length;i++){if(pre + values[i] > values[i]){pre = pre + values[i];rF = i;}else{pre = values[i];lF = i;rF = i;}if(maxSum < pre){maxSum = pre;l = lF;r = rF;}}return new int[]{l, r};}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/423934.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

spring mvc学习(54):简单异常处理

引入jar包 pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">…

spring mvc学习(55):简单异常处理二

引入jar包 pom.xml <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">…

Jelinek-Merer与Absolute discounting 平滑方法

Jelinek-Merer Jelinek-Merer平滑方法的基本思想是利用低元n-gram模型对高元n-gram模型进行线性插值。 PML(wi∣wi−1)c(wi,wi−1)c(wi−1)P_{ML}(w_i|w_{i-1})\dfrac{c(w_i,w_{i-1})}{c(w_{i-1})}PML​(wi​∣wi−1​)c(wi−1​)c(wi​,wi−1​)​ c(wi,wi−1)c(w_i,w_{i-1}…

从决策树到xgboost(一)

文章目录1 决策树1.1决策树定义1.2信息增益1.3 信息增益的算法1.4 信息增益比2 决策树ID32.1 ID3树的构建2.2 决策树的剪枝2.2.1 损失函数定义与计算2.2.2 剪枝过程2.3 CART树2.3.1 CART回归树2.3.2 CART分类树2.3.3 CART树剪枝1 决策树 1.1决策树定义 决策树的基本组成&…

从决策树到xgboost(二)

文章目录3 集成学习4 Adaboost4.1 Adaboost算法4.1.1 初始化训练数据的起始权值分布4.1.2 对m个弱分类器m1,2,3...M4.1.3 构建弱分类器的线性组合4.1.4 得到最终的分类器5 Boosting5.1 加法模型5.2 前向分布算法6 提升决策树BDT6.1 BDT算法6.2 回归问题提升树7 梯度提升决策树G…

[伤了昨天的心 裂成碎片和沙一起飞]五香里脊

今晨3点挂的电话&#xff0c;6点睁开的眼。困到头痛&#xff0c;但还是烧了道肉菜。…**…**…**…**…**…**…**…*分隔 五香里脊*…**…**…**…**…**…**…**…五香里脊 材料&#xff1a;1&#xff0e;里脊肉、油、麻油。2&#xff0e;蒜末、辣椒末、水、生抽、老醋、糖、…

MOTOMAN-SV3X运动学建模验证图

以下是正解&#xff0c;逆解 结果 转载于:https://www.cnblogs.com/wqj1212/archive/2008/01/01/1022177.html

小程序·云开发实战 - 迷你微博

0. 前言 本文将手把手教你如何写出迷你版微博的一行行代码&#xff0c;迷你版微博包含以下功能&#xff1a; Feed 流&#xff1a;关注动态、所有动态发送图文动态搜索用户关注系统点赞动态个人主页使用到的云开发能力&#xff1a; 云数据库云存储云函数云调用没错&#xff0c;几…

spring mvc学习(60):ssm项目整合

SSM整合 建立springmvc项目&#xff0c;先跑起来&#xff0c;再整合spring和mybatis 一.SpringMVC建立 1.新建maven工程&#xff0c;安装tomcat 2.导入pom <!-- springmvc --><dependency><groupId>org.springframework</groupId><artifactId>…

回溯算法归纳

回溯算法解题思路回溯的两种思路题目描述按照思路1解决按思路2解决回溯的两种思路 看不同的解题方法&#xff0c;形成不同的思维。 先说结论。回溯解题思路1&#xff1a;是对可选择每个元素&#xff0c;采取不选择、选择两种策略&#xff0c;不断递归下去。最近看花花酱的视频…

mybatis学习(1):【持久化框架】Mybatis简介与原理

从这篇博文开始我们学习一下Mybatis&#xff0c;希望大家提出宝贵的建议。 什么是Mybatis MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code&#xff0c;并且改名为MyBatis 。iBATIS一词来源于“internet”和“abati…

UML类图解义

小菜&#xff1a;“对了&#xff0c;我时常在一些技术书中看到这些类图表示&#xff0c;简单的还看得懂&#xff0c;有些标记我很容易混淆。要不你给我讲讲吧。” 大鸟&#xff1a;“这个其实多看多用就熟悉了。我给你举一个例子&#xff0c;来看这样一幅图&#xff0c;其中就包…

mybatis学习(2):基本设置和核心配置

1创建一个简单的java项目 2导入jar包 建立一个lib包 链接&#xff1a;https://pan.baidu.com/s/1eJ7xXF2qvUbgde2T--Sphg 提取码&#xff1a;3bgy 加入junit的包 右键项目---build path---add library 4导入配置文件 log4j.properties ### ### log4j.rootLogger ERROR,s…

mybatis学习(3):映射文件的配置和接口创建

配置文件 db.properties jdbc.drivercom.oracle.jdbc.OracleDriver jdbc.urljdbc:oracle:thin:127.0.0.1:1521:wiicare jdbc.usernamewiicare jdbc.passwordMdsd123 log4j.properties ### ### log4j.rootLogger ERROR,stdout### ¡ ### log4j.appender.stdout org.ap…

mybatis学习(4):工具类和实体类的创建

配置文件 db.properties jdbc.drivercom.oracle.jdbc.OracleDriver jdbc.urljdbc:oracle:thin:127.0.0.1:1521:wiicare jdbc.usernamewiicare jdbc.passwordMdsd123 log4j.properties ### ### log4j.rootLogger ERROR,stdout### ¡ ### log4j.appender.stdout org.ap…

windows下解决pip安装出错问题

今天使用pip install xxx突然报错&#xff0c;找了好久全是在linux上的解决方案&#xff0c;好不容易找到一个windows下的解决方案&#xff0c;所以将他记录下来。 解决方案&#xff1a; cmd中敲命令&#xff1a;python -m ensurepip 得到pip的setuptools 然后就可以用&#xf…

检测ID卡的输入或者是其它卡的输入。

由于ID卡或者是其它的一些不同类型的卡&#xff0c;它们只是负责模拟键盘的录入&#xff0c;但是&#xff0c;它们在录入的时候没有一个很好的标识位可以让我们知道当前输入的是客户手工录入的字符串还是ID卡读入的字符串&#xff0c;我相信&#xff0c;很多人在做这样的开发时…

mybatis学习(6):IntelliJ IDEA 如何创建一个普通的 Java 项目,及创建 Java 文件并运行

一、创建 Java 项目&#xff1a; 1、打开 IDEA 软件&#xff0c;点击界面上的 Create New Project 2、出现以下界面&#xff0c;选中 Java&#xff0c;然后选择 JDK&#xff0c;最后点击 Next&#xff0c;进行下一步&#xff08;我的是 jdk1.8&#xff09; 3、这里是选择生成项…

第一百五十期:Java程序员必备:异常的十个关键知识点

总结了Java异常十个关键知识点&#xff0c;面试或者工作中都有用哦&#xff0c;加油。异常是指阻止当前方法或作用域继续执行的问题。比如你读取的文件不存在&#xff0c;数组越界&#xff0c;进行除法时&#xff0c;除数为0等都会导致异常。 前言 总结了Java异常十个关键知识点…

学用 TStringGrid [1] - ColCount、RowCount、Cells

本例功能:1、获取 StringGrid 的行数、列数;2、给单元赋值.运行效果图://示例代码: unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls, ExtCtrls, Grids;typeTForm1 class(TForm)StringGrid1: TStringG…