问题描述:给定一只股票在某段时间内的历史价格变化曲线,找出一个能够实现收益最大化的时间段。

理解:为找出最大化的收益,需要考虑的是在买进和卖出时的价格变化幅度,因此从该股票的每日变化幅度来考虑问题比较合适。由此,可以将上述问题稍作变形:给定一只股票在某段时间内的每日变化幅度,找出一个合适的买进和卖出时间,以实现收益最大化。因此,将输入数据转换如下,并试图在整个时间段中找到一个累加和最大的子区间,亦即最大子数组。

暴力求解:首先能够想到的是在一个给定数组(区间)中,其子数组(子区间)的个数是C(2,n),很容易就能遍历完所有子数组从而找出最大的那个,其最坏情况渐进时间复杂度是Θ(n2)。假设每日变化幅度保存在数组A中(A的下标从1到n),A.length表示A的元素个数,最终结果以元组形式返回;给出伪码如下:BRUTE_FORCE(A)

i = 1

sum = -infinity

for i <= A.length, inc by 1

j = i

last_sum = 0

for j <= A.length, inc by 1

last_sum += A[j]

if last_sum > sum

sum = last_sum

start = i

end = j

return (start, end, sum)

java代码实现:private static void bruteForce(int[] arr) {

int start = -1, end = -1, max = 0;

for (int i = 0; i < arr.length; i++) {

// 定义lastSum的目的,每一轮新的循环都需要重新累计

int lastSum = 0;

for (int j = i; j < arr.length; j++) {

lastSum += arr[j];

if (lastSum > max) {

max = lastSum;

start = i;

end = j;

}

}

}

System.out.println("maxSum = " + max + ", start : " + start + ", end = " + end);

}

分治求解:分治策略通常包含:分解子问题,解决子问题,合并子问题。由此可以推出大致的解决思路:首先依然假设数据输入如上一个方法那样,然后考虑将A[1...n]拆分为规模大致相同的两个子数left[1...mid]和right[mid+1...n],其中mid=(1+n)/2向下取整,那么可以肯定,最大子数组要么在这两个子数组中,要么横跨这两个子数组,因此可以分别求解这三种情况,取其中最大的子数组并返回即可。

对于left/right子数组可递归求解,而对于横跨两个子数组的情况,如果能够使得该情况下的求解时间复杂度为O(n),那么应该能让整体的最坏时间复杂度低于Θ(n2)。如果仅仅是通过遍历所有包含A[mid]和A[mid+1]的子数组来找最大子数组,那么很显然仅求解该情况就需要Θ(n2)的时间。可以推断横跨两个子数组的最大子数组,必须由两个分别在left/right中的子数组组成,这两个子数组在分别包含了A[mid]和A[mid+1]的所有子数组中是最大的;因为如果存在一个不满足上述条件的最大子数组,那么总可以用上述方法找到一个更大的子数组。

根据上述思路,很容易推知求解横跨两个子数组的情况只需要O(n)的时间。由此给出伪码如下:

(1)子过程:找出横跨两个子数组的最大子数组FIND_CROSSING_MAX_SUBARRAY(A, low, mid, high)

left_sum = -infinity

sum = 0

i = mid

for i >= low, dec by 1

sum += A[i]

if sum > left_sum

left_sum = sum

left_index = i

right_sum = -infinity

sum = 0

i = mid + 1

for i <= high, inc by 1

sum += A[i]

if sum > right_sum

right_sum = sum

right_index = i

return (left_index, right_index, left_sum+right_sum)(2)主过程:分治法找出最大子数组FIND_MAX_SUBARRAY(A, low, high)

if low == high

return (low, high, A[low])

else

mid = down_trunc((low + high) / 2)

(left_start, left_end, left_sum) =

FIND_MAX_SUBARRAY(A, low, mid)

(right_start, right_end, right_sum) =

FIND_MAX_SUBARRAY(A, mid+1, high)

(cross_start, cross_end, cross_sum) =

FIND_CROSSING_MAX_SUBARRAY(A, low, mid, high)

if left_sum > right_sum and left_sum > cross_sum

return (left_start, left_end, left_sum)

else if right_sum > left_sum and right_sum > cross_sum

return (right_start, right_end, right_sum)

else

return (cross_start, cross_end, cross_sum)

可以看出上述算法渐进时间复杂度为Θ(nlg(n))。

java代码实现:private static int[] findMaxSubArray(int[] arr, int left, int right) {

// 分解到只有一个元素

if (left == right) {

return new int[]{left, right, arr[left]};

}

int mid = (left + right) / 2;

int[] leftResult = findMaxSubArray(arr, left, mid);

int[] rightResult = findMaxSubArray(arr, mid + 1, right);

int[] crossingResult = findCrossingMaxSubArray(arr, mid, left, right);

if (leftResult[2] > rightResult[2] && leftResult[2] > crossingResult[2]) {

return leftResult;

} else if (rightResult[2] > leftResult[2] && rightResult[2] > crossingResult[2]) {

return rightResult;

} else {

return crossingResult;

}

}

private static int[] findCrossingMaxSubArray(int[] arr, int mid, int left, int right) {

int leftIndex = -1, rightIndex = -1, leftMax = Integer.MIN_VALUE, rightMax = Integer.MIN_VALUE, sum = 0;

for (int i = mid; i >= left; i--) {

sum += arr[i];

if (sum > leftMax) {

leftIndex = i;

leftMax = sum;

}

}

sum = 0;

for (int i = mid + 1; i <= right; i++) {

sum += arr[i];

if (sum > rightMax) {

rightIndex = i;

rightMax = sum;

}

}

return new int[]{leftIndex, rightIndex, leftMax + rightMax};

}

缩减问题规模:

缩减问题规模的方法:在查找过程中,是否可以根据现有的信息,来缩减需要排查的子数组个数,进而获得更好的时间复杂度呢?一个思路是不再重复检查以前累加过的元素,即从左至右累加元素,保存其中的最大子数组,如果在加入一个元素后累加和为负数,则从该元素的后一个元素重新累加,直至整个数组遍历完毕。该思路有效的前提是证明以下几个假设:

1. 可以将最大子数组来源分为三种:已经遍历完的数组部分、未遍历的数组部分以及跨越这两部分的子数组

2. 可以假设当从左至右累加直至累加和为负,所得的最大子数组是当前已遍历完的数组部分中最大的

3. 可以假设当累加和为负时,潜在的最大子数组不可能从该元素或该元素左边的元素开始

假设1不证自明。

假设从A[1]累加到A[i]时第一次遇到其累加和为负(1<=i<=n),那么A[i]一定为负,且A[1]+...+A[i-1]>=0。当i<=2时,显然此时假设2成立。

当i>2时,可以认为在A[1]...A[i]中,所有子数组可分为三种:从A[1]开始向右拓展、从A[i]开始向左拓展以及不包含A[1]和A[i]的中间子数组;

显然从A[i]向左拓展的不可能是最大子数组,而如果不包含A[1]和A[i]的中间子数组是最大子数组,那么可以使该中间子数组加上其左边的部分构成一个新的子数组,而且该子数组总是大于等于这个中间子数组,因为其左边部分总是大于等于0,所以该情况下假设2也得证。综合来看假设2是成立的。

对于假设3,显然潜在的最大子数组不可能从A[i]开始,因为A[i]<0。当潜在的最大子数组从A[i]的左边开始时,假设其从A[j]开始(1<=j1时,A[j]+...+A[i]一定是负数,因为A[1]+...+A[j-1]一定大于等于0而A[1]+...+A[i]一定为负。所以综合来看,从A[i]或者A[i]的左边寻找潜在的子数组是没有意义的。

伪码如下,时间复杂度为Θ(n)。对于全部是负数的情况,特殊处理即可,不影响时间复杂度。LINEAR_SEARCH_MAX_SUBARRAY(A)

sum = -infinity

start = 0

end = 0

cur_sum = 0

cur_start_index = 1

i = 1

for i <= A.length, inc by 1

cur_sum += A[i]

if cur_sum < 0

cur_sum = 0

cur_start_index = i + 1

else

if sum < cur_sum

sum = cur_sum

start = cur_start_index

end = i

return (start, end, sum)

java代码如下:private static int[] linerSearchMaxSubArray(int[] nums) {

int start = 0;

int end = 0;

int max = 0;

int temp = 0;

int ts = 0;

for (int i = 0; i < nums.length; i++) {

temp += nums[i];

if (temp < 0) {

ts = i + 1;

temp = 0;

} else {

if (temp > max) {

start = ts;

end = i;

max = temp;

}

}

}

return new int[]{start, end, max};

}

最大子数组下标java,【算法】最大子数组相关推荐

  1. java+数组+列举,Java基础-基础语法-数组

    数组简介: 数组(Array)是Java 语言中内置的一种基本数据存储结构,通俗的理解,就是一组数的集合,目的是用来一次存储多个数据. 数组是程序中实现很多算法的基础,可以在一定程度上简化代码的书写. ...

  2. 数组结构与算法-007-015稀疏数组与队列

    007 稀疏数组的应用场景 基本介绍: 当一个数组中大部分元素为0,或者为同一个数值的数组时,可以稀疏数组来保存该数组 稀疏数组的处理方法是: 记录数组一共有几行几列,有多少个不同的值 把具有不同值的 ...

  3. python数组下标访问起始_为什么数组下标从0开始

    背景 有很多编程语言的数组都是从 0 开始编号,你是否下意识地想过,为什么数组要从 0 开始编号,而不是从 1 开始呢? 从 1 开始不是更符合人类的思维习惯吗?那先看下数组的定义. 数组(Array ...

  4. matlab返回数组下标,matlab中如何将数组下标定义为从0开始

    楼主不是想保留0:3这个信息?如果是的话,可以用结构数据来保存,或者是令k=0:3,然后显示的时候为t(k+1)不就行了.又或者编个函数,转换一下www.mh456.com防采集. 这个是没法直接修改 ...

  5. php查找二维数组下标,PHP实现二维数组中的查找算法小结

    本文实例讲述了PHP实现二维数组中的查找算法.分享给大家供大家参考,具体如下: 方法1:silu从左下角最后一行的第一个元素开始,遍历.如果小于target 则遍历该行的所有元素,找到结束.如果大于继 ...

  6. java中引用数组元素时 数组下标可以是_引用数组元素时,数组下标可以是( )...

    摘要: .引用元素顾公众它包者和竞争业内应商企业部因外部的供等因素和素括企客.引用元素,相互系环境活动内部各部关系作.之间门之间以及与竞争营销有协企业企业企业微观务等的关.服是指.下标叙述功方罗汉的练 ...

  7. 合并两个有序数组(java算法)

    给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目. 请你 合并 nums2 到 nums1 中,使合并 ...

  8. Java算法之旋转数组

    189 class Solution {public void rotate(int[] nums, int k) {int len = nums.length;int[] tmps = new in ...

  9. php判断数组下标,php判断json或者数组格式与给定格式是否一致

    开发的时候碰到个问题,我们技术人员规定了录数据的格式,例如:我们的json模板如下: { "sentence":"Martin and Kelvin went to Ch ...

最新文章

  1. idea 端口被占用
  2. 设置sqlplus环境变量
  3. perf stat 输出解读
  4. Linux Tomcat 安装
  5. 5、计算机网络物理层和数据链路层
  6. openresty入门示例
  7. python中如何打印两行代码间的空行_python 打印几行空行、 打印不换行
  8. 年度总结文章的抽奖结果公布
  9. 1067: 有问题的里程表
  10. 自制 .NET Core 路由调试中间件
  11. webpack5 模块联邦 single-spa基座微前端 systemjs
  12. JAVA面试要点003_Mybatis中#和$的区别
  13. python气象绘图_Python气象绘图教程特刊(一)
  14. 深入讲解防火墙的概念原理与实现
  15. js中for循环的优化写法
  16. HDU-单词的前缀 字典树
  17. centos7安装python开发环境(python3,postgresql,sublime,supervisor)
  18. matlab x的平均值符号怎么打出来,教你在Word中输入平均数的符号X上加一横(X拔)...
  19. 前端处理后端返回的excel文件流并下载
  20. 编程生成一个瓶子(1)

热门文章

  1. 利用SpringBoot+RabbitMQ,实现一个邮件推送服务
  2. 阿里技术专家都铎:一文搞懂技术债
  3. spring boot之session的创建,销毁,超时,监听等等小结
  4. Java描述设计模式(14):解释器模式
  5. Java DOC 转换给 PDF 格式文档的代码
  6. 在Kotlin中使用Gradle构建缓存
  7. Apache Hadoop下一代MapReduce框架(YARN)简介 (Apache Hadoop NextGen MapReduce (YARN))
  8. Radware:应用交付向云端扩展
  9. JavaScript触发事件大全--能力工场
  10. 微软MCITP系列课程(八)文件服务器及磁盘配额