918. Maximum Sum Circular Subarray - Explanation

Problem Link

Description

You are given a circular integer array nums of length n, return the maximum possible sum of a non-empty subarray of nums.

A circular array means the end of the array connects to the beginning of the array. Formally, the next element of nums[i] is nums[(i + 1) % n] and the previous element of nums[i] is nums[(i - 1 + n) % n].

A subarray may only include each element of the fixed buffer nums at most once. Formally, for a subarray nums[i], nums[i + 1], ..., nums[j], there does not exist i <= k1, k2 <= j with k1 % n == k2 % n.

Example 1:

Input: nums = [-2,4,-5,4,-5,9,4]

Output: 15

Explanation: Subarray [-2,4,9,4] has maximum sum 15.

Example 2:

Input: nums = [2,3,-4]

Output: 5

Constraints:

  • n == nums.length
  • 1 <= n <= 3 * 10,000
  • -30,000 <= nums[i] <= 30,000


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Kadane's Algorithm - Foundation for finding maximum subarray sum; extended here to handle circular arrays
  • Prefix and Suffix Sums - Used to efficiently compute wrap-around subarray sums
  • Circular Array Handling - Understanding how subarrays can wrap from end to beginning
  • Maximum Subarray Problem - This problem builds directly on the classic maximum subarray solution

1. Brute Force

Intuition

Since the array is circular, any contiguous subarray can wrap around from the end back to the beginning. The most direct approach is to try every possible starting position and extend the subarray up to the full length of the array, tracking the maximum sum found. Using modular indexing allows us to wrap around seamlessly. While simple to understand, this method is slow because it examines every possible subarray.

Algorithm

  1. Initialize res with the first element.
  2. For each starting index i from 0 to n - 1:
    • Reset cur_sum to 0.
    • Extend the subarray from index i up to i + n - 1, using j % n to wrap around.
    • Add each element to cur_sum and update res with the maximum.
  3. Return res.
class Solution:
    def maxSubarraySumCircular(self, nums: List[int]) -> int:
        n = len(nums)
        res = nums[0]

        for i in range(n):
            cur_sum = 0
            for j in range(i, i + n):
                cur_sum += nums[j % n]
                res = max(res, cur_sum)

        return res

Time & Space Complexity

  • Time complexity: O(n2)O(n ^ 2)
  • Space complexity: O(1)O(1) extra space.

2. Prefix & Suffix Sums

Intuition

A circular subarray with maximum sum either lies entirely within the array (no wrap), or it wraps around (takes a prefix and a suffix). For the non-wrapping case, we can use standard Kadane's algorithm. For the wrapping case, we want the best prefix ending at some index combined with the best suffix starting after that index. By precomputing the maximum suffix sum from each position, we can efficiently find the best combination of prefix and suffix sums in a single pass.

Algorithm

  1. Compute right_max[i] as the maximum suffix sum starting at index i or later, iterating from right to left.
  2. Initialize max_sum with nums[0], and set cur_max and prefix_sum to 0.
  3. Iterate from left to right:
    • Update cur_max using Kadane's logic: cur_max = max(cur_max, 0) + nums[i].
    • Update max_sum with cur_max (non-wrapping case).
    • Add nums[i] to prefix_sum.
    • If i + 1 < n, update max_sum with prefix_sum + right_max[i + 1] (wrapping case).
  4. Return max_sum.
class Solution:
    def maxSubarraySumCircular(self, nums: List[int]) -> int:
        n = len(nums)
        right_max = [0] * n
        right_max[-1] = nums[-1]
        suffix_sum = nums[-1]

        for i in range(n - 2, -1, -1):
            suffix_sum += nums[i]
            right_max[i] = max(right_max[i + 1], suffix_sum)

        max_sum = nums[0]
        cur_max = 0
        prefix_sum = 0

        for i in range(n):
            cur_max = max(cur_max, 0) + nums[i]
            max_sum = max(max_sum, cur_max)
            prefix_sum += nums[i]
            if i + 1 < n:
                max_sum = max(max_sum, prefix_sum + right_max[i + 1])

        return max_sum

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(n)O(n)

3. Kadane's Algorithm

Intuition

The maximum circular subarray sum falls into one of two cases: either the subarray does not wrap around, or it does. For the non-wrapping case, standard Kadane's algorithm finds the maximum subarray sum. For the wrapping case, if we remove a contiguous middle portion from the array, what remains is a prefix plus a suffix. Removing the minimum subarray sum leaves behind the maximum wrapping sum, which equals total - minSubarraySum. We take the better of these two cases, but if all elements are negative, the maximum is simply the largest single element.

Algorithm

  1. Initialize globMax and globMin to nums[0], and set curMax, curMin, and total to 0.
  2. For each num in nums:
    • Update curMax = max(curMax + num, num) and globMax = max(globMax, curMax).
    • Update curMin = min(curMin + num, num) and globMin = min(globMin, curMin).
    • Add num to total.
  3. If globMax > 0, return max(globMax, total - globMin). Otherwise, return globMax.
class Solution:
    def maxSubarraySumCircular(self, nums: List[int]) -> int:
        globMax, globMin = nums[0], nums[0]
        curMax, curMin = 0, 0
        total = 0

        for num in nums:
            curMax = max(curMax + num, num)
            curMin = min(curMin + num, num)
            total += num
            globMax = max(globMax, curMax)
            globMin = min(globMin, curMin)

        return max(globMax, total - globMin) if globMax > 0 else globMax

Time & Space Complexity

  • Time complexity: O(n)O(n)
  • Space complexity: O(1)O(1) extra space.

Common Pitfalls

Forgetting the All-Negative Edge Case

When all elements are negative, the minimum subarray equals the entire array, so total - minSum becomes zero. This would incorrectly suggest an empty subarray. The fix is to check if maxSum > 0 before considering the wrap-around case, or equivalently return maxSum when all elements are negative.

Confusing Wrap-Around Logic

A common mistake is trying to directly find the maximum wrap-around subarray. The key insight is that if a subarray wraps around, it means we are excluding a contiguous middle portion. Therefore, the wrap-around max equals total - minSubarraySum. Failing to realize this leads to overly complicated solutions.

Incorrect Kadane Initialization

Initializing curMax and curMin to nums[0] instead of 0 can cause off-by-one errors when updating the global max/min. The standard Kadane approach resets the running sum when it becomes detrimental, which requires careful initialization to avoid skipping or double-counting the first element.