LeetCode算法日记 - Day 82: 环形子数组的最大和

目录

[1. 环形子数组的最大和](#1. 环形子数组的最大和)

[1.1 题目解析](#1.1 题目解析)

[1.2 解法](#1.2 解法)

[1.3 代码实现](#1.3 代码实现)


1. 环形子数组的最大和

https://leetcode.cn/problems/maximum-sum-circular-subarray/

给定一个长度为 n 的环形整数数组 nums ,返回*nums 的非空 子数组 的最大可能和*。

环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。

子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。

示例 1:

复制代码
输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3

示例 2:

复制代码
输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

示例 3:

复制代码
输入:nums = [3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3

提示:

  • n == nums.length
  • 1 <= n <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104

1.1 题目解析

题目本质

在环形数组中寻找最大连续子数组和。环形意味着数组首尾相连,但子数组不能重复使用元素。

常规解法

用 动态规划 算法求最大子数组和。遍历数组,维护当前位置结尾的最大和,取全局最大值。

问题分析

普通 动态规划 算法只能处理线性数组,无法处理跨越边界的情况。

例如 5,-3,5,最优解是选首尾 5,5=10,但线性算法只会算出 5,-3,5=7。环形数组的最大子数组要么在中间,要么跨越首尾,需要分情况讨论。

思路转折
跨边界的情况可以转换思维------选首尾等价于不选中间。
如果我们能找到中间的最小子数组和,用总和减去它就得到了跨边界的最大和。因此需要同时维护两个 DP:一个求最大子数组和(不跨边界),一个求最小子数组和(用于计算跨边界)。

1.2 解法

算法思想:动态规划分两种情况,fi 表示以 i 结尾的最大子数组和,gi 表示以 i 结尾的最小子数组和。

状态转移:

  • fi = max(fi-1 + numsi, numsi)

  • gi = min(gi-1 + numsi, numsi)

**最终答案:**max(fmax, sum - gmin),其中 fmax 是不跨边界的最大值,sum - gmin 是跨边界的最大值。

**i)**初始化两个 DP 数组 f 和 g,分别用于记录最大和最小子数组和,初始值设为 0。

**ii)**第一次遍历计算数组总和 sum,这是计算跨边界情况的基础。

**iii)**第二次遍历同时更新 fi 和 gi,维护全局最大值 max 和全局最小值 min。

**iv)**计算跨边界的最大和 tMin = sum - min。如果 tMin == 0 说明所有数都是负数,只能返回 max(最大的负数),否则返回 max(tMin, max)。

易错点:

  • f 和 g 的含义理解

    • fi 表示以位置 i 结尾的最大子数组和,用于求不跨界情况。

    • gi 表示以位置 i 结尾的最小子数组和,用于计算跨界情况(通过 sum - gmin 得到跨界的最大值)。

  • 全负数的边界情况

    • 当数组全为负数时,gmin 会等于 sum(因为整个数组就是最小子数组),导致 sum - gmin = 0。

    • 这个 0 表示"什么都不选",但题目要求子数组非空,所以必须返回 fmax(即最大的那个负数)。判断条件是 tMin == 0 ? max : Math.max(tMin, max)。

  • max 和 min 的初始化:max 不能初始化为 0,必须是 Integer.MIN_VALUE/2,否则当所有数都是负数时,fmax 会被错误地判定为 0。同理,min 初始化为 Integer.MAX_VALUE/2。

  • 跨界计算的本质:sum - gmin 不是简单的数学运算,而是"总和减去不要的最小部分 = 保留的最大部分(跨边界)"。理解这个转换是关键,不要误以为是 sum + |gmin|。

1.3 代码实现

java 复制代码
class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        int n = nums.length;
        int[] f = new int[n + 1];  // 最大子数组和
        int[] g = new int[n + 1];  // 最小子数组和
        f[0] = g[0] = 0;
        
        int max = Integer.MIN_VALUE / 2;
        int min = Integer.MAX_VALUE / 2;
        int sum = 0;
        
        // 计算总和
        for (int i = 0; i < n; i++) {
            sum += nums[i];
        }
        
        // 同时计算最大和最小子数组和
        for (int i = 1; i <= n; i++) {
            f[i] = Math.max(f[i - 1] + nums[i - 1], nums[i - 1]);
            max = Math.max(max, f[i]);
            
            g[i] = Math.min(g[i - 1] + nums[i - 1], nums[i - 1]);
            min = Math.min(min, g[i]);
        }
        
        // 跨边界的最大和 = 总和 - 最小子数组和
        int tMin = sum - min;
        
        // 全是负数时返回最大的负数
        return tMin == 0 ? max : Math.max(tMin, max);
    }
}

复杂度分析:

  • **时间复杂度:O(n),**两次遍历数组。

  • **空间复杂度:O(n),**使用了两个 DP 数组。可优化至 O(1),只需维护前一个状态的值。

相关推荐
weixin_4604435613 小时前
2026年新修订《网络安全法》实施:Java私有化考试系统如何做好SBOM、漏洞治理与安全升级?
java·开发语言·在线考试系统·企业培训考试系统·宏远培训考试系统·国企培训考试系统·国产化培训系统
海鸥-w16 小时前
什么是AOP
java
Cc.Y17 小时前
Java零基础入门:可变字符串与包装类:StringBuilder、StringBuffer 与 通讯录管理系统实战
java·开发语言·python
启观川17 小时前
数据结构与算法 -第 3 章 常用算法-动态规划
数据结构·笔记·python·算法
码爸17 小时前
排序算法介绍
python·算法·排序算法
paopaokaka_luck17 小时前
基于springboot+vue的智慧家政管理系统(AI问答、 WebSocket实时沟通、ECharts运营分析、订单规则与服务追踪)
java·javascript·spring boot·数据分析·echarts·mybatis
sm_9267870517 小时前
RFID 标签打印的技术实现要点与二次开发实践
java·大数据·前端·c++·编辑器
阿森_dev17 小时前
2027 届毕设选题怎么选:30 个 Java 方向选题的实现难度排序
java·开发语言·spring boot·毕业设计·课程设计
Sarvartha17 小时前
Lambda 表达式和函数式接口
java·开发语言
计算机毕业设计杰瑞17 小时前
【计算机毕设精品】基于SpringBoot的一体化教务与学业质量管理系统的设计与实现,附源码_增删改查_java项目_可完美运行部署_网站项目
java·spring boot·课程设计