什么是最大子数组问题?

news2024/11/13 9:55:19

本文首发自「慕课网」,想了解更多IT干货内容,程序员圈内热闻,欢迎关注!

作者| 慕课网精英讲师 JdreamZhang

最大子数组(Max Subarray)问题,是计算机科学与技术领域中一种常见的算法问题,主要可以利用分治思想进行快速实现。

最大子数组问题描述如下:假如我们有一个数组,数组中的元素有正数和负数,如何在数组中找到一段连续的子数组,使得子数组各个元素之和最大。

最大子数组问题在生活中有很多实际情况可以与其对应,比如说我们观察某一股票在一段时间内的走势,请问如何找出在哪一天买入,哪一天卖出可以赚到最大差价(这里假设你已经知道股票的价格走势)?为了实现最大化的股票收益,我们需要考虑的是买进和卖出时候的价格变化幅度,因此从该股票的每日变化幅度来考虑这个问题更加合适。所以,我们可以将这个问题稍作变形:将股票价格走势对应为每日股票价格涨跌,涨记为正值,跌记为负值,然后一段时间就对应一个正负数数组,并试图找到该数组的最大子数组,就可以获得最大收益。

接下来,就让我们看看如何利用分治算法求解最大子数组问题吧。

1. 分治法求解最大子数组问题

在最大子数组问题之后,我们一起来看一下如何利用分治思想求解最大子数组问题。这里我们假设待初始的数组为 [12, -3, -16, 20, -19, -3, 18, 20, -7, 12, -9, 7, -10],记为数组 A,并用 A [low,high] 表示这个数组,其中 low,high 是这个数组的最小最大下标, low = 0,high = A.length -1 , 然后我们需要找到该数组的其中某一个最大子数组。

Tips: 这里我们需要注意,同一数组的最大子数组可能有多个,所以我们在这里求解的时候只说求解某一个最大子数组。

1.1 分治算法求解思路

在这里,我们用分治算法求解最大子数组问题,主要思路如下:

  1. 步骤 1:
  2. 找到数组 A 的中间元素,其下标记为 mid,根据分治策略,将数组 A [low,high] 根据中间元素划分为 A [low,mid], A [mid+1,high] 两个部分;
  3. 步骤 2:
  4. 假设数组 A 的最大子数组为 A [i, j],那么 A [i, j] 只有以下三种可能:
  5. a: 最大子数组 A [i, j] 完全位于 A [low, mid] 中,此时有 low <= i <= j <= mid;
  6. b: 最大子数组 A [i, j] 完全位于 A [mid+1, high] 中,此时有 mid+1 <= i <= j <= high;
  7. c: 最大子数组 A [i, j] 跨域了中间元素,则 low <= i <= mid <= j <= high。
  8. 分别计算上述三种对应的最大子数组的结果;
  9. Tips: 在这里,情况 a 和情况 b 这两种情况所得的子问题和之前求解数组 A 的最大子数组的问题形式完全一样,这里是分治思想的主要体现,将大的问题拆分成了两个相同形式的小问题;情况 c 这时候可以直接求解,在 3.2 节中会具体介绍其求解过程。
  10. 步骤 3
  11. 步骤 2 三种情况的求解结果进行比较,其中最大子数组的结果为最大值的情况就是我们的所求结果。

1.2 求解思路详解

首先,我们将 3.1 节中的求解思路用下图表示:

 如图,我们可以更清楚地明白求解一个数组的最大子数组问题就是对 1.1 节中的步骤 2 中的三种情况的分别求解, 步骤 2 中的情况 a 和情况 b 可以通过递归求解得出结果,所以我们现在先看一下情况 c 的具体求解过程。情况 c 的求解很容易在线性时间内就可以得出结果,他并不是原问题的一个规模更小的实例,因为情况 c 中加入了一个限制(求出的子数组必须跨越下标为 mid 的中间节点)。如上图的右边图形所示,情况 c 的求解结果都会有两个子数组 A [i,mid] 和 A [mid+1,j] 组成,其中 low <= i <= mid <= j <=high。因此,我们只需要找出形如 A [i,mid] 和 A [mid+1,j] 的最大子数组,然后将其合并即可。

我们用下面的伪代码 FindMaxCrossSubarray 描述 1.1 节中 步骤 2 中的情况 c 具体实现过程:

FindMaxCrossSubarray(A, low, mid ,high):
   leftSum = minInteger; //设置左边的最大连续和初始值为最小整数值
   sum =0;
   maxLeft = mid; //记录左边最大子数组的下标位置,初始化为mid
   for (i=mid; i>=low; i--){
       sum = sum + A[i];
       if (sum > leftSum){
           leftSum = sum;
           maxtLeft = i; 
       }
   }
   rightSum = minInteger; //设置右边的最大连续和初始值为最小整数值
   sum = 0;
   maxtRight = mid + 1; //记录右边最大子数组的下标位置,初始化为mid+1
   for (j=mid+1; j<=low; j++){
       sum = sum + A[j];
       if (sum > rightSum){
           rightSum = sum;
           maxtRight = j;//记录左边最大子数组的下标位置
       }
   }
   //返回结果是一个三元组数据,分别是最大子数组的开始下标,结束下标,求和的值
   return (maxLeft,maxRight,leftSum+rightSum);  
代码块1234567891011121314151617181920212223

上述伪代码的描述中的第 2 至第 11 行,是求解左半部分 A [low,mid] 的最大子数组的过程,因为必须包含下标为 mid 的元素,所以从下标为 mid 的中间节点开始逐步递减到下标为 low 的元素,对应伪代码中的第 5 至第 11 行的 for 循环结构,循环的过程中会记录下左边部分的最大子数组的具体值及左半部分的下标位置。同理,上述伪代码的第 12 至第 21 行对应的是求解右半部分 A [mid+1,high] 的最大子数组的过程。最后,伪代码中的第 23 行综合左右两边求解结果,返回必须跨越下标为 mid 的中间元素时,对应的原数组 A 的最大子数组结果。

当我们可以清楚地知道步骤 2 中的情况 c 的求解过程时,我们就可以综合知道对于数组 A 求解最大子数组的整体过程,用伪代码 FindMaxSubarray 描述如下:

FindMaxSubarray(A,low,high):
	 if (high == low){
            return new Result(low,high,A[low]);  //基础情况,只有一个元素时候的处理情况
     }else {
         //对应2.1节中步骤1,找到中间元素
         int mid = (low + high)/2;
         //对应2.1节中步骤2,分别对应a,b,c三种情况求解最大子数组结果
         (leftLow,leftHigh,leftSum) = FindMaxSubarray(A,low,mid);
         (rightLow,rightHigh,rightSum) = FindMaxSubarray(A,mid+1,high);
         (crossLow,crossHigh,crossSum) = FindMaxCrossSubarray(A,low,mid,high);
         //对应2.1节中步骤3,比较得出最后结果
         if(leftSum >= righSum && leftSum >= crossSum){
                return (leftLow,leftHigh,leftSum);
         }else if (rightSum >= leftSum && rightSum >= crossSum){
                return (rightLow,rightHigh,rightSum);
         }else {
                return (crossLow,crossHigh,crossSum);
         }
     }
代码块12345678910111213141516171819

上述求解数组 A 的最大子数组的整体过程伪代码,主要就是由我们在 1.1 节中描述的三大步骤而来。代码第 2 至第 4 行,主要表明了最基础的情况的处理方式。代码第 4 至第 18 行对应着具体的实现方式,第 8 行和第 9 行分别是对子问题的递归求解,第 10 行调用 FindMaxCrossSubarray 过程,求解跨越中间节点的最大子数组求解方式,最后第 12 至 18 行对比三种情况的结果得出最终结果。

2.JAVA 代码实现

在说明求解最大子数组的整个过程之后,接下来,我们看看如何用 java 代码实现最大子数组问题的求解。

package divide_and_conquer;

public class MaxSubarray {

    //内部类,用来存储最大子数组的返回结果,
    private static class Result {
        int low;
        int high;
        int sum;

        public Result(int low, int high, int sum) {
            this.low = low;
            this.high = high;
            this.sum = sum;
        }

        @Override
        public String toString() {
            return "Result{" +
                    "low=" + low +
                    ", high=" + high +
                    ", sum=" + sum +
                    '}';
        }
    }

    private static Result FindMaxCrossSubarray(int[]A,int low, int mid, int high){

        //寻找左边的连续最大值及记录位置
        int leftSum = Integer.MIN_VALUE;
        int sum = 0;
        int maxLeft = mid;
        for (int i=mid; i>=low; i--){
            sum = sum + A[i];
            if(sum > leftSum){
                leftSum = sum;
                maxLeft = i;
            }
        }

        //寻找右边的连续最大值及记录位置
        int rightSum = Integer.MIN_VALUE;
        int maxRight = mid+1;
        sum = 0;
        for ( int j=mid+1; j<=high;j++){
            sum = sum + A[j];
            if(sum > rightSum){
                rightSum = sum;
                maxRight = j;
            }
        }

        //返回跨越中间值的最大子数组结果
        return new Result(maxLeft,maxRight,leftSum + rightSum);
    }


    public static  Result FindMaxSubarray(int[] A, int low, int high){
        //数组只有一个元素时的处理情况
        if (high == low){
            return new Result(low,high,A[low]);
        }else {
            //对应思路中步骤1,找到中间元素
            int mid = (low + high)/2;
            //对应思路中步骤2,分别对应a,b,c三种情况求解最大子数组结果
            Result leftResult = FindMaxSubarray(A,low,mid);
            Result rightResult = FindMaxSubarray(A,mid+1,high);
            Result crossResult = FindMaxCrossSubarray(A,low,mid,high);
            //对应步骤3,比较
            if(leftResult.sum >= rightResult.sum && leftResult.sum >= crossResult.sum){
                return leftResult;
            }else if (rightResult.sum >= leftResult.sum && rightResult.sum >= crossResult.sum){
                return rightResult;
            }else {
                return crossResult;
            }
        }
    }

    public static void main(String[] args){
        int[] A = {12, -3, -16, 20, -19, -3, 18, 20, -7, 12, -9, 7, -10};
        System.out.println(FindMaxSubarray(A,0,A.length-1).toString());
    }
}
代码块123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384

运行结果如下:

Result{low=6, high=9, sum=43}
代码块1

运行结果中的 low 表示最大子数组在数组 A 中的开始下标,high 表示最大子数组在数组 A 中的终止下标,sum 表示最大子数组的求和值,对应到我们的实例数组 A 中,对应的最大最大子数组为 [18,20,-7,12]。

代码中第 5 行至 25 行的 Result 内部类,主要是用来存储最大子数组的返回结果,定义了子数组的开始下标,结束下标,求和值。代码的第 27 至 55 行是最大子数组跨越中间节点时候的最大子数组求解过程。代码的第 58 至 78 行是整个最大子数组的求解过程。代码的第 81 行和 82 行是求解最大子数组过程的一个示例,输出最大子数组的求解结果。

3. 小结

本篇文章主要利用分治思想求解最大子数组问题,需要大家充分熟悉了解分治算法的算法流程,知道分治算法在解决最大子数组时的实现思路,可以自己用代码实现最大子数组问题的求解。这次我们通过最大子数组这一实例介绍了分治算法的实际应用,希望可以帮助大家更好地理解分治算法。

欢迎关注「慕课网」,发现更多IT圈优质内容,分享干货知识,帮助你成为更好的程序员!

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

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

相关文章

CVE-2022-26135 Atlassian Jira Mobile Plugin SSRF漏洞分析

漏洞描述 6月29日&#xff0c;Atlassian官方发布安全公告&#xff0c;在Atlassian Jira 多款产品中存在服务端请求伪造漏洞(SSRF)&#xff0c;经过身份验证的远程攻击者可通过向Jira Core REST API发送特制请求&#xff0c;从而伪造服务端发起请求&#xff0c;从而导致敏感信息…

【青训营】规则引擎概述和入门

本文内容总结自 字节跳动青年训练营 第五届后端组 一、规则引擎是什么 规则引擎是一种嵌入在应用程序中的组件&#xff0c;实现了将业务决策从应用程序代码中分离出来&#xff0c;并且使用预定义语义模块编写业务决策。接受数据输入&#xff0c;解释业务规则&#xff0c;并且…

Python接口测试实战5(上) - Git及Jenkins持续集成

本节内容接上节内容&#xff1a;在框架搭建好的基础上注册Github并新建仓库使用Git上传项目Jenkins的安装Jenkins接口测试项目的配置注册Github并新建仓库Git简介Git(读音为/gɪt/。)是一个开源的分布式版本控制系统版本控制&#xff1a; 对项目及代码记录每次提交和修改&#…

【Docker】(六)使用network完成容器间的网络通信

1.前言 本系列文章记录了从0开始学习Docker的过程&#xff0c;Docker系列历史文章&#xff1a; &#xff08;一&#xff09;基本概念与安装使用 &#xff08;二&#xff09;如何使用Docker发布一个SpringBoot服务 &#xff08;三&#xff09;使用registry远程镜像仓库管理镜像…

12、特征值与特征向量

目录 一、特征值和特征向量的定义 二、特征值和特征向量的相关函数 三、特征值和特征向量的计算 一、特征值和特征向量的定义 假设A是一个nn的矩阵&#xff0c;A的特征值问题就是找到下面方程组的解&#xff1a; 其中&#xff0c;λ为标量&#xff0c;V为矢量&#xff0c;若…

【堆的认识及其优先级队列】java代码实现,保姆级教程学习堆和优先级队列

前言&#xff1a; 大家好&#xff0c;我是良辰丫&#x1f49e;&#x1f49e;⛽&#xff0c;我们又见面了&#xff0c;前面我们讲了用链表实现的二叉树&#xff0c;今天我们来接触堆的概念&#xff0c;堆是一种特殊的二叉树&#xff0c;只不过咱们的对底层原理是数组&#xff0c…

大数据监控平台-Prometheus监控Hadoop

简介 本篇主要是使用jmx配合Prometheus监控大数据平台 前提 链接&#xff1a;https://pan.baidu.com/s/1c6nsjOKw4-a_Wqr82l0QhQ 提取码&#xff1a;yyds --来自百度网盘超级会员V5的分享 先安装好Prometheus Flink(Pometheus监控)_顶尖高手养成计划的博客-CSDN博客_${en…

金融实践 | 信创存储 打造安全可控的金融数据底座

本文刊登于《金融电子化》杂志 2023 年 1 月上&#xff0c;作者为中国出口信用保险公司信息科技部张倩&#xff0c;曲文非&#xff0c;庞松松&#xff0c;康达。 2022 年初&#xff0c;中国人民银行《金融科技发展规划&#xff08;2022—2025 年&#xff09;》和银保监会《关于…

JAVA语言实验 实验 ( 二 )

JAVA语言实验 :实验 &#xff08; 一 &#xff09; JAVA语言实验 :实验 &#xff08; 二 &#xff09; JAVA语言实验 :实验 &#xff08; 三 &#xff09; 一、实验目的 &#xff08;1&#xff09;熟悉 Java 图形界面的基本设计。 &#xff08;2&#xff09;熟悉 Java 界面的菜…

【LeetCode每日一题】【2023/1/31】2319. 判断矩阵是否是一个 X 矩阵

文章目录2319. 判断矩阵是否是一个 X 矩阵方法1&#xff1a;直接遍历2319. 判断矩阵是否是一个 X 矩阵 LeetCode: 2319. 判断矩阵是否是一个 X 矩阵 简单\color{#00AF9B}{简单}简单 如果一个正方形矩阵满足下述 全部 条件&#xff0c;则称之为一个 X 矩阵 &#xff1a; 矩阵对…

spring boot文档阅读笔记——02

spring boot文档阅读笔记——01 目录标题一、日志&#xff08;一&#xff09;slf4j logback&#xff08;spring boot默认方式&#xff09;1. 获取日志对象方式&#xff1a;2. 设置日志级别&#xff1a;3. 设置日志格式&#xff1a;4. 输出到日志文件&#xff1a;5. 日志文件设置…

关于python的mediapipe库踩过的坑

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a;lqj_本人的博客_CSDN博客-微信小程序,前端,vue领域博主lqj_本人擅长微信小程序,前端,vue,等方面的知识https://blog.csdn.net/lbcyllqj?spm1000.2115.3001.5343 哔哩哔哩欢迎关注&…

Nginx 常用配置汇总!

众所周知&#xff0c;Nginx 是 Apache服务不错的替代品。其特点是占有内存少&#xff0c;并发能力强&#xff0c;事实上 Nginx 的并发能力在同类型的网页服务器中表现较好&#xff0c;因此国内知名大厂例如&#xff1a;淘宝&#xff0c;京东&#xff0c;百度&#xff0c;新浪&a…

1.10 golang 切片Slice

1. 切片Slice 需要说明&#xff0c;slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段&#xff0c;以实现变长方案。 1. 切片&#xff1a;切片是数组的一个引用&#xff0c;因此切片是引用类型。但自身是结构体&#xff0c;值拷贝传递。2. 切片的长度可以改变…

零基础机器学习做游戏辅助第六课--猫狗数据集认识卷积神经网络(二)

一、初识卷积 上一课我们已经将图像数据进行了预处理,这节课的重点就是学习卷积神经网络,到底什么是卷积,我们看图 input是我们输入的图像,Kernel是我们设置的3x3卷积核,卷积层将图像和卷积核进行计算提取特征输出神经元。

代码随想录算法训练营第35天 回溯算法 java :455.分发饼干 376. 摆动序列53. 最大子序和

文章目录贪心算法思路LeetCode 455.分发饼干题目详解LeetCode 376. 摆动序列题目详解思路示图LeetCode 53. 最大子序和题目详解思路示图总结贪心算法思路 以局部最优带动全局最优 LeetCode 455.分发饼干 题目详解 我做的是采用 优先满足胃口的思路。 对每个孩子 i&#xff…

【c#系列】PDF进行操作-浏览、分割、合并、插入、删除(2)

这节我们主要实现缩小、旋转、打印、分割、合并、放大等功能 1、 放大功能 单击放大按钮&#xff0c;实现PDF放大预览&#xff0c;效果如下&#xff1a; 设计代码&#xff1a; System.Windows.Forms.ToolStripButton FangDaBT_Tool;FangDaBT_Tool new System.Windows.Form…

GBase GCDW云数仓阿里云版免费试用来了!

GBase GCDW云原生数据仓库&#xff08;GCDW&#xff09;在阿里云计算巢上提供免费试用了&#xff01;简单 3 步&#xff0c;即可获得一个免费试用的GCDW服务实例&#xff0c;您可以定制该服务实例的云主机规格和数据库计算服务节点数等实例参数&#xff0c;该免费试用支持的数据…

LabVIEW NI CompactRIO控制器:性能和吞吐量基准测试

LabVIEW NI CompactRIO控制器&#xff1a;性能和吞吐量基准测试CompactRIO控制器基于LabVIEW RIO架构&#xff0c;采用了功能强大的64位Intel Atom E3800片上系统(SoC)和Xilinx Kintex7 FPGA等最新技术。Intel Atom SoC提供了极高的性能和丰富的功能&#xff0c;包括集成式GPU和…

数据结构实验二 :二叉树的操作与实现

数据结构实验一:线性表,堆栈和队列实现 数据结构实验二 :二叉树的操作与实现 数据结构实验三: 图的操作与实现 数据结构实验四 : 查找和排序算法实现 文章目录一、实验目的&#xff1a;二、使用仪器、器材三、实验内容及原理1、教材P247实验题1&#xff1a;实现二叉树的各种基本…