Leetcode-918.「环形子数组的最大和」

30 阅读3分钟

核心难点在于数组是环形的 —— 最大子数组可能是「普通的连续子数组」,也可能是「跨数组首尾的环形子数组」。 环形数组的最大子数组和只有两种可能:

  1. 情况 1:最大子数组是「非环形」的(和普通的最大子数组和一样),比如 [1,-2,3,-2] 的最大子数组是 [3]
  2. 情况 2:最大子数组是「环形」的(跨首尾),比如 [5,-3,5] 的最大子数组是 [5(末尾),5(开头)],和为 10。

关键推导:

  • 情况 1 的解:用经典的「Kadane 算法」求普通最大子数组和(记为 max_normal);
  • 情况 2 的解:数组总和 total - 最小子数组和(记为 min_sub)(因为「跨首尾的最大和」= 总和 - 中间那段最小的子数组和);
  • 最终结果:取 max(max_normal, total - min_sub)
  • 边界特例:如果数组全是负数(max_normal < 0),则直接返回 max_normal(因为此时 total - min_sub = 0,但实际最大子数组和是数组中最大的那个负数)。

经典 Kadane 算法回顾(求普通最大子数组和)

Kadane 算法核心:遍历数组,维护「当前子数组和」,如果当前和为负,就重置为当前元素(因为负数会拉低后续和),同时更新全局最大值。

// 求普通最大子数组和
int kadane_max(vector<int>& nums) {
    int cur_max = nums[0], max_sum = nums[0];
    for (int i = 1; i < nums.size(); ++i) {
        cur_max = max(nums[i], cur_max + nums[i]);
        max_sum = max(max_sum, cur_max);
    }
    return max_sum;
}

// 求最小子数组和(Kadane变种)
int kadane_min(vector<int>& nums) {
    int cur_min = nums[0], min_sum = nums[0];
    for (int i = 1; i < nums.size(); ++i) {
        cur_min = min(nums[i], cur_min + nums[i]);
        min_sum = min(min_sum, cur_min);
    }
    return min_sum;
}

完整代码实现

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        int n = nums.size();
        if (n == 1) return nums[0]; // 边界:单元素数组直接返回
        
        // 1. 计算数组总和
        int total = 0;
        for (int num : nums) total += num;
        
        // 2. 求普通最大子数组和(情况1)
        int cur_max = nums[0], max_normal = nums[0];
        // 求最小子数组和(用于情况2)
        int cur_min = nums[0], min_sub = nums[0];
        
        for (int i = 1; i < n; ++i) {
            // 更新最大子数组和
            cur_max = max(nums[i], cur_max + nums[i]);
            max_normal = max(max_normal, cur_max);
            
            // 更新最小子数组和
            cur_min = min(nums[i], cur_min + nums[i]);
            min_sub = min(min_sub, cur_min);
        }
        
        // 3. 处理全负数的情况:如果max_normal < 0,说明所有元素都是负数,直接返回max_normal
        if (max_normal < 0) {
            return max_normal;
        }
        
        // 4. 情况2:环形最大和 = 总和 - 最小子数组和
        int max_circular = total - min_sub;
        
        // 5. 最终结果取两种情况的最大值
        return max(max_normal, max_circular);
    }
};

关键细节解释

  1. 为什么环形和 = 总和 - 最小子数组和? 比如数组 [5,-3,5],总和是 7,最小子数组和是 - 3,环形和 = 7 - (-3)=10(对应子数组 [5,5]);本质是:环形子数组是「去掉中间一段最小的子数组」,剩下的首尾拼接就是最大的环形和。

  2. 全负数的边界处理? 比如数组 [-3,-2,-1]max_normal = -1(最大的单个元素),total - min_sub = -6 - (-6) = 0,但 0 并不是合法的子数组和(子数组不能为空),所以此时直接返回 max_normal

  3. 时间 / 空间复杂度

    • 时间复杂度:O (n)(仅遍历数组一次);
    • 空间复杂度:O (1)(仅用常数变量)。