Maximum Sum Circular Subarray
Problem
Given a circular integer array nums, return the maximum possible sum of a non-empty subarray of nums.
- 1 ≤ nums.length ≤ 3 * 10⁴
- −3 * 10⁴ ≤ nums[i] ≤ 3 * 10⁴
Example
nums = [1,-2,3,-2]3The maximum subarray sum without wrapping is 3 (subarray [3]). The maximum subarray sum with wrapping is 1 + (-2) + (-2) = -3, which is less than 3. Thus, the answer is 3. The algorithm computes the total sum, the maximum subarray sum using Kadane's algorithm, and the minimum subarray sum similarly. Since the array is circular, the maximum sum could be either the maximum subarray sum found directly or the total sum minus the minimum subarray sum (which corresponds to the maximum sum of the wrapped subarray). The critical insight is to consider both cases and choose the maximum.
Approach
Straightforward Solution
A brute-force approach would consider all subarrays including those that wrap around, resulting in O(n^2) or worse time complexity, which is infeasible for large inputs.
Core Observation
The maximum subarray sum in a circular array is either the maximum subarray sum in the linear array or the total sum minus the minimum subarray sum (which corresponds to the maximum sum of the wrapped subarray). This duality arises because wrapping the subarray is equivalent to excluding a minimum subarray in the middle.
Path to Optimal
The key recognition signals are 'maximum subarray sum' and 'circular array'. These indicate Dynamic Programming because the problem breaks down into finding maximum and minimum subarray sums using Kadane's algorithm. The circular nature suggests considering the complement of the minimum subarray to find the maximum wrapped subarray sum. By computing total sum, max subarray sum, and min subarray sum in a single pass, the problem reduces to choosing the maximum between max subarray sum and total minus min subarray sum.
Optimal Approach
Use Kadane's algorithm to find the maximum subarray sum and minimum subarray sum in one pass while accumulating the total sum. If the maximum subarray sum is negative, it means all numbers are negative, so return the maximum subarray sum directly. Otherwise, return the maximum between the maximum subarray sum and total sum minus the minimum subarray sum. This approach runs in O(n) time and O(1) space.
Time
O(n)
The algorithm iterates through the array once, updating running sums and tracking maximum and minimum subarray sums, resulting in linear time complexity.
Space
O(1)
Only a fixed number of variables are used to track sums and maxima/minima, so the auxiliary space is constant regardless of input size.
Pattern Spotlight
Dynamic Programming (Kadane's Algorithm with Circular Array Adaptation)
When dealing with maximum subarray sums in circular arrays, the problem splits into two cases: the maximum subarray is either fully contained (linear Kadane) or wraps around the ends (total sum minus minimum subarray). Computing both maximum and minimum subarrays in one pass enables an O(n) solution that elegantly handles circularity.
Solution
| 1 | class Solution: |
| 2 | def maxSubarraySumCircular(self, nums: list[int]) -> int: |
| 3 | total = nums[0] |
| 4 | |
| 5 | curr_max = nums[0] |
| 6 | best_max = nums[0] |
| 7 | |
| 8 | curr_min = nums[0] |
| 9 | best_min = nums[0] |
| 10 | |
| 11 | for i in range(1, len(nums)): |
| 12 | num = nums[i] |
| 13 | total += num |
| 14 | |
| 15 | curr_max = max(num, curr_max + num) |
| 16 | best_max = max(best_max, curr_max) |
| 17 | |
| 18 | curr_min = min(num, curr_min + num) |
| 19 | best_min = min(best_min, curr_min) |
| 20 | |
| 21 | if best_max < 0: |
| 22 | return best_max |
| 23 | |
| 24 | return max(best_max, total - best_min) |
Step-by-Step Solution
Accumulate Total and Track Maximum and Minimum Subarray Sums
| 3 | total = nums[0] |
| 5 | curr_max = nums[0] |
| 6 | best_max = nums[0] |
| 8 | curr_min = nums[0] |
| 9 | best_min = nums[0] |
| 11 | for i in range(1, len(nums)): |
| 12 | num = nums[i] |
| 13 | total += num |
| 15 | curr_max = max(num, curr_max + num) |
| 16 | best_max = max(best_max, curr_max) |
| 18 | curr_min = min(num, curr_min + num) |
| 19 | best_min = min(best_min, curr_min) |
Objective
To compute the total sum of the array while simultaneously finding the maximum and minimum subarray sums using Kadane's algorithm variants.
Key Insight
Kadane's algorithm efficiently finds the maximum subarray sum by tracking the current maximum sum ending at each position. Similarly, tracking the minimum subarray sum allows identifying the subarray that, when excluded, yields the maximum wrapped subarray sum. By combining these computations in a single pass, the algorithm leverages the relationship between total sum, maximum subarray, and minimum subarray to solve the circular problem efficiently.
Interview Quick-Check
Core Logic
Kadane's algorithm is applied twice in one pass: once to find the maximum subarray sum and once to find the minimum subarray sum, while accumulating the total sum.
State & Boundaries
Initialize current and best max/min sums to the first element to handle arrays with all negative numbers correctly.
Common Pitfalls & Bugs
Failing to update total sum or mixing max and min calculations can lead to incorrect results.
Complexity
This single-pass approach ensures O(n) time and O(1) space complexity.
Handle All Negative Numbers and Return Final Maximum Sum
| 21 | if best_max < 0: |
| 22 | return best_max |
| 24 | return max(best_max, total - best_min) |
Objective
To handle the edge case where all numbers are negative and return the correct maximum subarray sum, otherwise return the maximum between the linear max subarray sum and the wrapped subarray sum.
Key Insight
If the maximum subarray sum is negative, it means all elements are negative, so the maximum subarray sum is the largest (least negative) element. In this case, the wrapped sum calculation (total minus minimum subarray sum) would be zero or invalid. Otherwise, the maximum sum is the greater of the linear maximum subarray sum and the wrapped sum, which accounts for circular subarrays.
Interview Quick-Check
Core Logic
Return best_max directly if it is negative to correctly handle arrays with all negative numbers.
Core Logic
Otherwise, return the maximum between best_max and total minus best_min to cover both linear and wrapped subarray cases.
Common Pitfalls & Bugs
Neglecting the all-negative case leads to incorrect results when the wrapped sum is zero or less than the maximum element.
Line Analysis
This solution has 4 Critical lines interviewers watch for.
curr_max = max(num, curr_max + num)
Update current maximum subarray sum ending at this element.
This line embodies Kadane's algorithm's core logic, deciding whether to start a new subarray at the current element or extend the existing one, which is essential for finding the maximum subarray sum efficiently.
curr_min = min(num, curr_min + num)
Update current minimum subarray sum ending at this element.
This line applies the minimum variant of Kadane's algorithm, deciding whether to start a new minimum subarray or extend the existing one, which is crucial for computing the wrapped subarray sum.
if best_max < 0:
Check if the maximum subarray sum is negative (all elements negative).
This condition identifies the all-negative scenario, ensuring the algorithm returns the correct maximum subarray sum without incorrectly using the wrapped sum formula.
return max(best_max, total - best_min)
Return the maximum between the linear max subarray sum and the wrapped subarray sum.
This return statement captures the core insight of the problem: the maximum circular subarray sum is either the maximum linear subarray sum or the total sum minus the minimum subarray sum, covering both non-wrapping and wrapping cases.
curr_max = nums[0]
Initialize current maximum subarray sum to the first element.
This initialization is critical for Kadane's algorithm to correctly track the maximum subarray sum starting from the first element.
best_max = nums[0]
Initialize best maximum subarray sum to the first element.
This variable stores the global maximum subarray sum found so far, starting with the first element to handle edge cases.
curr_min = nums[0]
Initialize current minimum subarray sum to the first element.
Tracking the minimum subarray sum is necessary to find the subarray that, when excluded, yields the maximum wrapped subarray sum.
best_min = nums[0]
Initialize best minimum subarray sum to the first element.
This variable stores the global minimum subarray sum found so far, starting with the first element to handle edge cases.
best_max = max(best_max, curr_max)
Update global maximum subarray sum if current max is greater.
Maintains the best maximum subarray sum found so far, ensuring the final answer considers all subarrays.
best_min = min(best_min, curr_min)
Update global minimum subarray sum if current min is smaller.
Keeps track of the smallest subarray sum found so far, which is necessary to compute the maximum wrapped subarray sum.
return best_max
Return the maximum subarray sum if all elements are negative.
Returns the correct result for the all-negative case, avoiding incorrect wrapped sum calculations.
total += num
Add current element to total sum.
Accumulates the sum of all elements, which is essential for calculating the wrapped subarray sum.
for i in range(1, len(nums)):
Start iterating over nums from the second element.
The loop processes each element to update total, maximum, and minimum subarray sums, enabling a single-pass solution.
total = nums[0]
Initialize total sum with the first element of nums.
Starting total with the first element sets the baseline for accumulating the sum of all elements, which is essential for calculating the wrapped subarray sum later.
num = nums[i]
Assign current element to num for clarity.
Using a descriptive variable name improves readability and reduces repeated indexing.
Test Your Understanding
Why do we return the maximum between the maximum subarray sum and total sum minus the minimum subarray sum?
Because the maximum subarray sum in a circular array can either be a normal subarray (found by Kadane's algorithm) or a wrapped subarray that excludes the minimum subarray; subtracting the minimum subarray sum from the total sum gives the sum of this wrapped subarray. Taking the maximum of these two covers all cases.
Related Problems
Dynamic Programming pattern
Don't just read it. Drill it.
Reconstruct Maximum Sum Circular Subarray from memory until it sticks. AlgoDrill blanks out key lines and makes you fill them back in, step by step.
Unlock the Maximum Sum Circular Subarray drill