一、题目

二、做题思路
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);
}
}
};
八、流程图
