918. Maximum Sum Circular Subarray - Explanation
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: 15Explanation: Subarray [-2,4,9,4] has maximum sum 15.
Example 2:
Input: nums = [2,3,-4]
Output: 5Constraints:
n == nums.length1 <= n <= 3 * 10,000-30,000 <= nums[i] <= 30,000
Topics
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
- Initialize
reswith the first element. - For each starting index
ifrom0ton-1:- Reset
cur_sumto0. - Extend the subarray from index
iup toi+n-1, usingj%nto wrap around. - Add each element to
cur_sumand updatereswith the maximum.
- Reset
- Return
res.
Time & Space Complexity
- Time complexity:
- Space complexity: 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
- Compute
right_max[i]as the maximum suffix sum starting at indexior later, iterating from right to left. - Initialize
max_sumwithnums[0], and setcur_maxandprefix_sumto0. - Iterate from left to right:
- Update
cur_maxusing Kadane's logic:cur_max=max(cur_max, 0)+nums[i]. - Update
max_sumwithcur_max(non-wrapping case). - Add
nums[i]toprefix_sum. - If
i+1<n, updatemax_sumwithprefix_sum+right_max[i + 1](wrapping case).
- Update
- 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_sumTime & Space Complexity
- Time complexity:
- Space complexity:
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
- Initialize
globMaxandglobMintonums[0], and setcurMax,curMin, andtotalto0. - For each
numinnums:- Update
curMax=max(curMax+num, num)andglobMax=max(globMax, curMax). - Update
curMin=min(curMin+num, num)andglobMin=min(globMin, curMin). - Add
numtototal.
- Update
- If
globMax>0, returnmax(globMax, total - globMin). Otherwise, returnglobMax.
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 globMaxTime & Space Complexity
- Time complexity:
- Space complexity: 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.
Sign in to join the discussion