最大子数组和问题-详解Kadane算法

    • 一、问题定义与暴力解法
      • 1.1 问题描述
      • 1.2 暴力解法的低效性
    • 二、Kadane算法的核心原理
      • 2.1 动态规划思想的应用
      • 2.2 优化空间复杂度
    • 三、Kadane算法的Java实现
      • 3.1 基础版本(处理所有情况)
      • 3.2 算法正确性验证
    • 四、Kadane算法的变种与拓展
      • 4.1 变种1:输出最大子数组的起止索引
      • 4.2 变种2:限制子数组长度(最多`k`个元素)
      • 4.3 变种3:允许子数组为空(返回0)
    • 五、时间与空间复杂度
    • 六、实际应用场景

最大子数组和(Maximum Subarray Sum)是一个经典且高频的数组算法考点,这个问题看似简单——从一个整数数组中找到和最大的连续子数组,但暴力求解的效率极低。Kadane算法(卡丹算法)作为专门解决此问题的高效方法,能在O(n)O(n)O(n)时间内完成求解,是动态规划思想的典型应用。本文我将深入解析Kadane算法的核心原理、实现细节、变种拓展及实际应用,结合Java代码示例,帮你彻底掌握这一高效算法。

一、问题定义与暴力解法

1.1 问题描述

给定一个整数数组nums(可能包含负数),找到一个连续子数组(至少包含一个元素),使得该子数组的和最大,返回这个最大和。

例如:

  • 输入:nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
  • 输出:6(对应子数组[4, -1, 2, 1]

1.2 暴力解法的低效性

最直观的思路是枚举所有可能的子数组,计算其和并记录最大值:

// 暴力解法:枚举所有子数组
public int maxSubArrayBruteForce(int[] nums) {int maxSum = Integer.MIN_VALUE;int n = nums.length;for (int i = 0; i < n; i++) {  // 子数组起点int currentSum = 0;for (int j = i; j < n; j++) {  // 子数组终点currentSum += nums[j];maxSum = Math.max(maxSum, currentSum);}}return maxSum;
}
  • 时间复杂度O(n2)O(n^2)O(n2)(嵌套循环枚举所有子数组)。
  • 局限性:当n超过10410^4104时,会因超时无法通过测试,必须寻找更高效的算法。

二、Kadane算法的核心原理

2.1 动态规划思想的应用

Kadane算法的本质是动态规划,其核心是用局部最优推导全局最优

  • 定义dp[i]为“以nums[i]为结尾的最大子数组和”。
  • 对于每个元素nums[i],有两种选择:
    1. nums[i]加入之前的子数组(即dp[i-1] + nums[i])。
    2. nums[i]为起点重新开始一个子数组(即nums[i])。
  • 因此,状态转移方程为:
    dp[i] = max(nums[i], dp[i-1] + nums[i])
  • 全局最大和即为所有dp[i]中的最大值。

2.2 优化空间复杂度

由于dp[i]仅依赖于dp[i-1],无需存储整个dp数组,可改用一个变量currentMax记录当前值,将空间复杂度从O(n)O(n)O(n)优化至O(1)O(1)O(1)

  1. 初始化currentMax = nums[0](以第一个元素为结尾的子数组和),maxSum = nums[0](全局最大值)。
  2. 从第二个元素开始遍历:
    • currentMax = max(nums[i], currentMax + nums[i])(更新局部最优)。
    • maxSum = max(maxSum, currentMax)(更新全局最优)。
  3. 遍历结束后,maxSum即为结果。

三、Kadane算法的Java实现

3.1 基础版本(处理所有情况)

public class KadaneAlgorithm {public int maxSubArray(int[] nums) {if (nums == null || nums.length == 0) {return 0;  // 边界处理:空数组}int currentMax = nums[0];  // 以当前元素为结尾的最大子数组和int maxSum = nums[0];      // 全局最大子数组和for (int i = 1; i < nums.length; i++) {// 选择:加入之前的子数组 或 重新开始currentMax = Math.max(nums[i], currentMax + nums[i]);// 更新全局最大值maxSum = Math.max(maxSum, currentMax);}return maxSum;}public static void main(String[] args) {KadaneAlgorithm solution = new KadaneAlgorithm();int[] nums = {-2, 1, -3, 4, -1, 2, 1, -5, 4};System.out.println(solution.maxSubArray(nums));  // 输出 6}
}

3.2 算法正确性验证

以示例nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]为例,分步验证:

索引inums[i]currentMax(局部最优)maxSum(全局最优)
0-2-2-2
11max(1, -2+1)=1max(-2, 1)=1
2-3max(-3, 1-3)=-2max(1, -2)=1
34max(4, -2+4)=4max(1, 4)=4
4-1max(-1, 4-1)=3max(4, 3)=4
52max(2, 3+2)=5max(4, 5)=5
61max(1, 5+1)=6max(5, 6)=6
7-5max(-5, 6-5)=1max(6, 1)=6
84max(4, 1+4)=5max(6, 5)=6

最终maxSum=6,与预期结果一致,验证了算法的正确性。

四、Kadane算法的变种与拓展

4.1 变种1:输出最大子数组的起止索引

除了最大和,有时需要返回子数组的具体位置(起点和终点索引)。只需在更新currentMaxmaxSum时,同步记录索引:

public int[] maxSubArrayWithIndex(int[] nums) {if (nums == null || nums.length == 0) {return new int[]{-1, -1};  // 空数组返回无效索引}int currentMax = nums[0];int maxSum = nums[0];int start = 0, end = 0;  // 当前子数组起止索引int tempStart = 0;       // 临时起点(用于重新开始子数组时更新)for (int i = 1; i < nums.length; i++) {if (nums[i] > currentMax + nums[i]) {// 重新开始子数组,更新临时起点currentMax = nums[i];tempStart = i;} else {// 加入之前的子数组currentMax += nums[i];}// 更新全局最大和及起止索引if (currentMax > maxSum) {maxSum = currentMax;start = tempStart;end = i;}}return new int[]{start, end, maxSum};  // 起点、终点、最大和
}

4.2 变种2:限制子数组长度(最多k个元素)

问题:找到长度不超过k的连续子数组的最大和。
思路:结合Kadane算法和前缀和优化,用滑动窗口维护前缀和的最小值(需额外处理长度限制):

public int maxSubArrayWithLimit(int[] nums, int k) {int n = nums.length;int[] prefixSum = new int[n + 1];  // 前缀和:prefixSum[i] = sum(nums[0..i-1])for (int i = 0; i < n; i++) {prefixSum[i + 1] = prefixSum[i] + nums[i];}int maxSum = Integer.MIN_VALUE;// 用单调队列维护前缀和的最小值(窗口内)Deque<Integer> deque = new ArrayDeque<>();deque.offer(0);  // 初始前缀和prefixSum[0] = 0for (int i = 1; i <= n; i++) {// 移除窗口外的前缀和(超过k个元素)while (!deque.isEmpty() && i - deque.peekFirst() > k) {deque.pollFirst();}// 当前前缀和 - 窗口内最小前缀和 = 最大子数组和maxSum = Math.max(maxSum, prefixSum[i] - prefixSum[deque.peekFirst()]);// 维护单调队列(保证前缀和递增)while (!deque.isEmpty() && prefixSum[i] <= prefixSum[deque.peekLast()]) {deque.pollLast();}deque.offer(i);}return maxSum;
}

4.3 变种3:允许子数组为空(返回0)

若题目允许子数组为空(即最大和可以是0,如所有元素为负数时),只需在最后对结果取max(0, maxSum)

public int maxSubArrayAllowEmpty(int[] nums) {int currentMax = 0;  // 初始化为0(允许空数组)int maxSum = 0;for (int num : nums) {currentMax = Math.max(0, currentMax + num);  // 空数组对应0maxSum = Math.max(maxSum, currentMax);}return maxSum;
}

五、时间与空间复杂度

  • 时间复杂度O(n)O(n)O(n),仅需遍历数组一次,每次操作均为常数时间。
  • 空间复杂度O(1)O(1)O(1),仅使用固定数量的变量(基础版本),适合大规模数据。

六、实际应用场景

  1. 股票收益分析:计算某段时间内连续持有股票的最大收益(将股价数组转为每日涨跌数组)。
  2. 信号处理:在噪声信号中提取连续有效信号段(信号强度和最大的区间)。
  3. 能源消耗优化:找到连续时间段内能源消耗最高的区间,用于负载均衡。
  4. LeetCode经典题:LeetCode 53(最大子数组和)、LeetCode 152(乘积最大子数组,Kadane算法的变种)。

总结
Kadane算法通过动态规划思想,以O(n)O(n)O(n)时间和O(1)O(1)O(1)空间高效解决最大子数组和问题,是算法优化的典型案例。其核心在于“局部最优选择”——每个元素要么加入之前的子数组,要么重新开始,通过不断更新局部最优和全局最优得到结果。
在实际应用中需注意:

  • 基础版本可直接解决无长度限制的最大子数组和问题。
  • 变种问题(如限制长度、返回索引)可通过扩展算法逻辑实现。
  • 结合前缀和、单调队列等工具,可处理更复杂的场景。

That’s all, thanks for reading~~
觉得有用就点个赞、收进收藏夹吧!关注我,获取更多干货~

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

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

相关文章

Mongoose网络库深度解析:从单线程到多线程的架构演进

0. 引言&#xff1a;C/C网络编程的困境与突破 在C/C开发领域&#xff0c;网络编程一直是一个令人头疼的问题。与Python的requests库或Go的net/http包不同&#xff0c;C/C缺乏统一的包管理体系和标准化的网络API。开发者往往需要面对gcc/msvc版本差异、平台兼容性问题、以及各种…

Jfinal+SQLite处理 sqlite数据库执行FIND_IN_SET报错

方法一原代码sql " and FIND_IN_SET(s.M_ID," ids ")"; 修改为 sql " where s.M_ID"getInSql(ids);public static String getInSql(String ids) {String[] idArray ids.split(",");StringBuilder sql new StringBuilder(" I…

day24——Java高级技术深度解析:单元测试、反射、注解与动态代理

文章目录一、单元测试&#xff1a;JUnit框架精要1.1 单元测试核心概念1.2 JUnit快速入门实战基础步骤&#xff1a;断言机制验证结果1.3 JUnit核心注解解析二、反射机制&#xff1a;框架设计的基石2.1 反射核心概念2.2 获取Class对象的三种方式2.3 反射操作类成分获取并执行构造…

网页的性能优化,以及具体的应用场景

下面是每个性能优化技术的具体应用场景示例&#xff0c;结合代码说明如何在实际项目中使用这些优化方法&#xff1a; 1. 批量DOM操作与DocumentFragment 应用场景&#xff1a;动态渲染大量列表项&#xff08;如评论区、商品列表&#xff09; 问题&#xff1a;逐个添加DOM元素会…

Fiddler 中文版 API 调试与性能优化实践 官方中文网全程支持

在现代开发中&#xff0c;性能问题往往是产品上线后最容易被忽视的一环&#xff0c;尤其是API接口性能。一旦接口响应时间过长或在高并发场景下出现性能瓶颈&#xff0c;可能直接影响用户体验和系统稳定性。对于开发者来说&#xff0c;如何精确地找到瓶颈所在&#xff0c;如何模…

嵌入式硬件篇---机械臂运动学解算(3自由度)

实际 3 自由度机械臂的解算是机器人控制的核心&#xff0c;涉及运动学正解&#xff08;关节角度→末端位姿&#xff09;和逆解&#xff08;目标位姿→关节角度&#xff09;。以下从结构建模、解算方法、代码实现和应用场景四个维度详细展开&#xff0c;结合工业级机械臂的典型场…

在摄像机视图中想像在普通 3D 视口里那样随意移动

有两条最常用的方法&#xff1a;1. 「锁定相机到视图」(Lock Camera to View)步骤进入相机视图&#xff1a;按 Numpad 0&#xff08;若无数字键盘&#xff0c;可在 Edit → Preferences → Input 勾选 Emulate Numpad 后用主键盘 0&#xff09;。右侧呼出 N 面板&#xff0c;切…

An End-to-End Attention-Based Approach for Learning on Graphs NC 2025

NC 2025 | 一种基于端到端注意力机制的图学习方法 Nature Communications IF=15.7 综合性期刊 1区 参考:https://mp.weixin.qq.com/s/cZ-d8Sf8wtQ9wfcGOFimCg 今天介绍一篇发表在 Nature Communications 的图学习论文《An end-to-end attention-based approach for learnin…

【牛客刷题】小红的数字串

文章目录 一、题目描述 1.1 输入描述 1.2 输出描述 1.3 示例1 二、高效解法 2.1 核心算法设计 2.2 算法设计理念 2.2.1 算法流程详解 2.2.2 复杂度分析 2.3 算法优势分析 2.3.1 关键优化点 2.3.2 正确性验证 2.4 边界处理 2.5 总结与扩展 一、题目描述 小红拿到了一个数字串(由…

微算法科技技术创新,将量子图像LSQb算法与量子加密技术相结合,构建更加安全的量子信息隐藏和传输系统

随着信息技术的发展&#xff0c;数据的安全性变得尤为重要。在传统计算模式下&#xff0c;即便采用复杂的加密算法&#xff0c;也难以完全抵御日益增长的网络攻击威胁。量子计算技术的出现为信息安全带来了新的解决方案。然而&#xff0c;量子图像处理领域仍面临复杂度高、效率…

博客摘录「 Springboot入门到精通(超详细文档)」2025年7月4日

1.Spring Boot返回Json数据及数据封装1. Controller 中使用RestController注解即可返回 Json 格式的数据首先看看RestController注解包含了什么东西&#xff0c; ResponseBody 注解是将返回的数据结构转换为 Json 格式Target({ElementType.TYPE}) Retention(RetentionPolicy.RU…

企业安全防护:堡垒机技术解析

目录 一、堡垒机&#xff1a;企业IT运维的安全守门人 1.1 核心价值矩阵 1.2堡垒机典型部署架构 二、堡垒机如何构建安全防线 2.1 四层防护体系 2.2 关键工作流程 三、堡垒机关键技术指标对比表 四、智能堡垒机的发展趋势 一、堡垒机&#xff1a;企业IT运维的安全守门人…

传输层协议 TCP

TCP 协议TCP 全称为 "传输控制协议(Transmission Control Protocol"). 人如其名, 要对数据的传输进行一个详细的控制TCP 协议段格式源/目的端口号: 表示数据是从哪个进程来, 到哪个进程去32 位序号/32 位确认号4 位 TCP 报头长度: 表示该 TCP 头部有多少个 32 位 bit…

RT-Thread的概念和移植

一、操作系统的概念 操作系统&#xff08;英语&#xff1a;Operating System&#xff0c;缩写&#xff1a;OS&#xff09;是一组主管并控制计算机操作、运用和运行硬件、软件资源和提供公共服务来组织用户交互的相互关联的系统软件程序。根据运行的环境&#xff0c;操作系统可以…

基于单片机倾角测量仪/角度测量/水平仪

传送门 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目速选一览表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品题目功能速览 概述 本设计实现了一种基于单片机的高精度数字倾角测量仪。系统核心由倾角传感器&#xff08;ADXL345倾…

深度学习 -- 初步认识Torch

深度学习 – 初步认识Torch 文章目录深度学习 -- 初步认识Torch一&#xff0c;认识人工智能1.1 人工智能的本质1.2 人工智能的实现过程二&#xff0c;认识Torch2.1简介2.2 概述2.3 Tensor的创建2.3.1 torch.tensor2.3.2 torch.Tensor三&#xff0c;创建线性和随机张量3.1创建线…

BGP的“聪明选路”遇上了TCP的“路径洁癖”,需人工调和

在路由器R1上有两条外网&#xff0c;WAN1和WAN2。R1上做了域名分流功能&#xff0c;全局网址分到WAN1&#xff0c;指定域名分到WAN2&#xff08;优先级更高&#xff09;。症状是用户反馈部分网页无法打开。于是各种检查尝试...... 2天过去了......最终结论是&#xff1a;即使S…

ACWing算法笔记 | 二分

&#x1f50d; C 二分查找双模板详解&#xff1a;左闭右开 vs 左闭右闭&#xff08;二分笔记&#xff09;二分查找&#xff08;Binary Search&#xff09;是一类高效的搜索算法&#xff0c;在 O(log n) 的时间复杂度下查找答案&#xff0c;适用于单调性问题。C STL 的 lower_bo…

centos 新加磁盘分区动态扩容

你不能直接将一个分区分配给/dev/mapper/centos-root&#xff0c;因为这是一个逻辑卷&#xff08;属于 LVM 系统&#xff09;。不过&#xff0c;你可以通过以下步骤将/dev/sda3添加到现有卷组或创建新的逻辑卷&#xff1a; 确认磁盘和分区信息 首先检查分区是否已格式化以及是否…

python学智能算法(二十六)|SVM-拉格朗日函数构造

【1】引言 前序学习进程中&#xff0c;已经了解了拉格朗日乘数法求极值的基本原理&#xff0c;也了解了寻找最佳超平面就是寻找最佳分隔距离。 这篇文章的学习目标是&#xff1a;使用拉格朗日乘数法获取最佳的分隔距离。 【2】构造拉格朗日函数 目标函数 首先是目标函数f&a…