《LeetCode 53 最大子数组和 || LeetCode 918 环形子数组的最大和》

一、题目

二、做题思路

2.1 状态表示(核心基础)

本题要求计算数组中连续子数组的最大和 。我们定义 dp[i] 表示以 nums[i] 结尾的连续子数组的最大和。

2.2 状态转移方程(关键难点)

对于以 nums[i] 结尾的子数组,有两种可能:

  • 将 nums[i] 接在前一个子数组后面 ,即 dp[i-1] + nums[i];

  • 重新开始一个新的子数组 ,即仅包含 nums[i] 本身。

    为了获得最大和,我们取两者中的较大值:

    dp[i] = max(dp[i-1] + nums[i], nums[i])。

2.3 初始化(边界防护)

第一个元素(下标 0)没有前驱子数组,因此 dp[0] = nums[0]。

2.4 填表顺序(递推方向)

dp[i] 仅依赖 dp[i-1],因此必须从左到右 (即 i 从 1 到 n-1)依次填充 dp 表,确保每个状态计算时,其前置状态已就绪。

2.5 返回值(目标映射)

题目要求返回整个数组的最大子数组和 ,即所有以各位置结尾的子数组和中的最大值:max(dp[0], dp[1], ..., dp[n-1]) 。可在递推过程中同步维护一个变量 ret 记录最大值,最终返回 ret。

三、代码

cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        // 边界处理:若数组为空,返回0(题目通常保证非空,但为健壮性加上)
        if (n == 0) return 0;

        // 1. 创建dp表
        // dp[i] 表示以 nums[i] 结尾的连续子数组的最大和
        vector<int> dp(n);

        // 2. 初始化:第一个元素结尾的最大和就是它本身
        dp[0] = nums[0];

        // 3. 填表顺序:从左到右(i 从 1 到 n-1)
        for (int i = 1; i < n; i++) {
            // 4. 状态转移方程:
            // 要么将当前元素 nums[i] 加入到前一个子数组(dp[i-1] + nums[i]),
            // 要么重新开始一个新的子数组(仅包含当前元素 nums[i]),取两者较大值
            dp[i] = max(dp[i - 1] + nums[i], nums[i]);
        }

        // 5. 返回值:所有 dp[i] 中的最大值,即为整个数组的最大子数组和
        int ret = -0x3f3f3f3f; // 初始化为极小值
        for (int i = 0; i < n; i++) {
            ret = max(ret, dp[i]);
        }
        return ret;

        // 优化:可以在循环中同时维护最大值,避免最后的遍历
        // int ret = dp[0];
        // for (int i = 1; i < n; i++) {
        //     dp[i] = max(dp[i-1] + nums[i], nums[i]);
        //     ret = max(ret, dp[i]);
        // }
        // return ret;
    }
};

四、流程图

五、题目

六、做题思路

6.1 状态表示(核心基础)

本题要求计算环形数组中非空子数组的最大和 。环形数组意味着答案可能来自跨越首尾的连续子数组,也可能是普通线性子数组。为同时处理两种情况,我们定义两类状态:

  • f[i] 表示以 nums[i] 结尾的线性(非环形)最大子数组和。

  • g[i] 表示以 nums[i] 结尾的线性(非环形)最小子数组和。

6.2 状态转移方程(关键难点)

对于线性子数组,转移与"最大子数组和"完全一致:

  • f[i] = max(nums[i], f[i-1] + nums[i])(取延续之前或重新开始的最大值)。

  • g[i] = min(nums[i], g[i-1] + nums[i])(取延续之前或重新开始的最小值)。

6.3 初始化(边界防护)

第一个元素(下标 0)没有前驱,因此 f[0] = g[0] = nums[0]。

6.4 填表顺序(递推方向)

每个 i 状态仅依赖 i-1,因此必须从左到右 (即 i 从 1 到 n-1)依次填充两个数组,确保每个状态计算时,其所有前置状态均已就绪。

6.5 返回值(目标映射)

环形最大子数组和有两种可能:

  • 普通线性最大 :即 max(f[i])。

  • 跨越首尾 :等价于总和减去线性最小子数组和 (即 total - min(g[i])),因为去掉中间一段最小和,剩下的就是环形子数组。

    但需注意特殊边界:若数组中所有元素均为负数 (此时 total == min(g[i])),则 total - min(g[i]) = 0,表示空子数组,不符合"非空"要求,此时应返回普通线性最大。因此最终返回 max(普通最大, 总和 - 普通最小),若总和等于最小子数组和,则直接返回普通最大。

七、代码

cpp 复制代码
class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        const int INF = 0x3f3f3f3f; // 定义一个足够大的数,用于表示正负无穷
        int n = nums.size();

        // 1. 创建dp表
        // f[i] : 以 nums[i] 结尾的(非环形)最大子数组和
        // g[i] : 以 nums[i] 结尾的(非环形)最小子数组和
        vector<int> f(n);
        vector<int> g(n);

        // 2. 初始化:第一个元素结尾的最大和最小都等于 nums[0]
        f[0] = g[0] = nums[0];

        // 3. 填表顺序:从左到右(i 从 1 到 n-1),因为只依赖前一个状态
        for (int i = 1; i < n; i++) {
            // 4. 状态转移方程:
            // 最大子数组和:要么延续之前的子数组(f[i-1]+nums[i]),要么重新开始(nums[i]),取较大
            f[i] = max(nums[i], f[i - 1] + nums[i]);
            // 最小子数组和:要么延续之前的子数组(g[i-1]+nums[i]),要么重新开始(nums[i]),取较小
            g[i] = min(nums[i], g[i - 1] + nums[i]);
        }

        // 5. 计算非环形最大子数组和(普通情况)
        int maxNormal = -INF;
        for (int i = 0; i < n; i++) 
        {
            maxNormal = max(maxNormal, f[i]);
        }

        // 计算非环形最小子数组和(用于环形情况的补集)
        int minNormal = INF;
        for (int i = 0; i < n; i++) 
        {
            minNormal = min(minNormal, g[i]);
        }

        // 计算数组总和
        int total = 0;
        for (int num : nums) {
            total += num;
        }

        // 环形最大子数组和 = max(非环形最大, 总和 - 非环形最小)
        // 但若数组全为负数,则"总和 - 非环形最小"会等于0(因为最小子数组和等于总和),
        // 此时环形方案会取空子数组(不允许),所以应返回非环形最大(即最大的负数)。
        if (total == minNormal) 
        {
            // 全为负数(或全为非正且最小子数组就是整个数组),返回非环形最大
            return maxNormal;
        } else 
        {
            return max(maxNormal, total - minNormal);
        }
    }
};

八、流程图

相关推荐
多弗朗皮卡丘3 分钟前
C++特殊类设计
开发语言·c++·设计模式
志起计算机编程31 分钟前
Clickhouse的几个关键优势
算法
重生之小比特35 分钟前
【算法拾光 7】双指针|两数之和 II - 输入有序数组 + 三数之和
算法
文丑颜不良啊1 小时前
LeetCode851~875题目解析
java·leetcode
Navigator_Z1 小时前
LeetCode //C - 1304. Find N Unique Integers Sum up to Zero
c语言·算法·leetcode
Yyyyyy~1 小时前
【C++】String
c++
会编程的小孩10 小时前
C++ qt的安装
c++
企业数字化笔记12 小时前
视频人体姿态怎么做三维可视化?2D关键点、深度局限与技术选型
人工智能·算法·音视频
?abc!13 小时前
(数组)581. 最短无序连续子数组
数据结构·算法·leetcode