Builds on Kadane's Algorithm, which you haven’t finished yet.
Kadane's Algorithm on a Circular Array (Maximum Sum Circular Subarray)
min and keep the total.You get an integer array nums that is circular: the number after the last one is the first one again. A subarray is a run of numbers that sit next to each other, and it may wrap around the end.
Find the largest sum of a non-empty subarray. A subarray can use each position at most once.
[3] has the largest sum, 3.Circular Kadane
best plain run, or total - worst plain run ➔ Kadane twice, once with max and once with min ➔ guard the all-negative array| 1 | def max_subarray_circular(nums: list[int]) -> int: |
| 2 | """Maximum Sum Circular Subarray: the best plain run, or the total minus the worst plain run. |
| 3 | Time: O(N) | Space: O(1) |
| 4 | """ |
| 5 | # best, worst: the best and the worst sum of a plain subarray; curr_max, curr_min: the same for one that ends at the current number |
| 6 | total = curr_max = curr_min = best = worst = nums[0] |
| 7 | for x in nums[1:]: |
| 8 | # 1. Run Kadane For The Best And For The Worst: |
| 9 | # The same extend-or-restart rule, once with max for the best run and once with min for the worst run. |
| 10 | curr_max = max(x, curr_max + x) |
| 11 | curr_min = min(x, curr_min + x) |
| 12 | best = max(best, curr_max) |
| 13 | worst = min(worst, curr_min) |
| 14 | total += x |
| 15 | |
| 16 | # 2. Choose The Plain Run Or The Wrapping Run: |
| 17 | # A run that wraps around is the total minus a plain run in the middle, so remove the worst plain run. |
| 18 | # If every number is negative, the worst plain run is the whole array and the wrapping run would be empty, so keep best. |
| 19 | return max(best, total - worst) if best > 0 else best |
x, best and worst are the best and the worst sum of a plain subarray so far, and total is the sum of the numbers seen.A run in a circular array is a plain run, or it wraps around the end, and a wrapping run is the whole array without one plain run in the middle. So run Kadane's Algorithm twice, once with max for best and once with min for worst, keep total, and the answer is the larger of best and total - worst. The trap is an array of only negative numbers: worst is then the whole array, total - worst is 0, and that is an empty run, so return best when it is not positive.
See it run
Circular Kadane, step by step
curr_max -2curr_min -2best -2worst -2total -2
Each column is one index of nums. The rows curr_max and curr_min fill in as the steps run.
xthe number being readcurr_maxthe largest sum of a run that ends at xcurr_minthe smallest sum of a run that ends at x
Start: total, curr_max, curr_min, best and worst are all nums[0] = -2.
Gotcha 1 of 3
- The trap
Gotcha 1: Taking
total - worstwhen every number is negativeMistakereturn max(best, total - worst)Fixreturn max(best, total - worst) if best > 0 else bestWhat happens: When every number is negative, the worst plain run is the whole array, so
total - worstis 0. That would be an empty run, which is not allowed. The answer is the largest single number, whichbestalready holds.Example: Maximum Sum Circular Subarray (LC 918): nums = [-3, -2, -3]Mistake: returns 0Fix: returns -2
- The array is not circular, so a run never wraps around the end and one Kadane pass is enough, as in Maximum Subarray (LC 53). See Kadane's Algorithm
Deep Dive
Why a wrapping run is the total minus a plain run
In a circular array, the last number is followed by the first number. A run can start near the end and continue at the start. Such a run wraps around.
Place the array on a circle and choose a run. The numbers that are not in the run form one unbroken piece of the circle. If the run wraps around the end of the list, that remaining piece lies inside the list, in the middle. It is a plain subarray.
So the sum of a wrapping run is total minus the sum of a plain run in the middle. To make the wrapping run as large as possible, remove the plain run with the smallest sum. That sum is worst.
Example: for nums = [5, -3, 5], total is 7 and worst is -3. The wrapping run has the sum 7 - (-3) = 10. It is the two 5s that touch at the wrap. A plain pass finds only 7.
Two Kadane passes in one loop
Maximum Subarray (LC 53) taught the extend-or-restart rule with max. The same rule with min gives the smallest sum of a subarray that ends at x. The blueprint keeps curr_max and curr_min, and it updates best and worst from them.
Both passes use the same loop, so the time stays O(N). total adds one more number per step. The space is O(1), with five variables.
The answer is max(best, total - worst). best covers the runs that do not wrap. total - worst covers the runs that wrap.
Trace for nums = [5, -3, 5]: best is 7, worst is -3 and total is 7. The answer is max(7, 10) = 10.
The empty run and the guard
If the removed plain run is the whole array, then total - worst is 0. That is an empty run, and a run must hold at least one number.
This happens when every number is negative. Then worst is the whole array. The correct answer is the largest single number, which is best.
The guard if best > 0 else best handles it. If best is positive, an empty run (value 0) can never be larger than best, so max(best, total - worst) is safe. If best is 0 or negative, return best.
Example: for [-3, -2, -3], total is -8, worst is -8, and the answer is -2. For the single number [-5], the answer is -5.
Checks and a related problem
I compared max_subarray_circular with a brute force that tries every start and every length on the circle. They agreed on 10,000 random arrays of 1 to 9 numbers, including arrays of only negative numbers and arrays of one number.
Example with a longer wrap: nums = [-2, 4, -5, 4, -5, 9, 4] has best = 13, worst = -6 and total = 9. The answer is max(13, 9 + 6) = 15. The run is 9, 4, -2, 4, which wraps around the end.
Maximum Absolute Sum of Any Subarray (LC 1749) uses the same two passes. The answer is max(best, -worst), because the absolute value of the smallest sum can be the largest.
60-Second Pitch
"A run in a circular array either stays inside the list or wraps around the end. If it wraps, the numbers outside the run form one plain piece in the middle. So a wrapping run is total minus a plain subarray, and the largest one removes the smallest plain subarray. I run Kadane's Algorithm twice in one loop, with max for best and with min for worst, and I add up total. The answer is max(best, total - worst). If every number is negative, total - worst is an empty run, so I return best. The time is O(N) and the space is O(1)."
Common Questions
- Why does removing the smallest plain run give the best wrapping run?
A wrapping run is the whole circle without one plain run in the middle of the list. Its sum is
totalminus the sum of that plain run. To make the result as large as possible, remove the plain run with the smallest sum, which isworst.- What does the function return when every number is negative?
The largest single number, which is
best. Thenworstis the whole array, sototal - worstis 0. That would be an empty run, which is not allowed. For[-3, -2, -3], the answer is -2.- What is the time and space complexity?
O(N)time, because one loop runs both Kadane passes and the total.O(1)space, because the function keeps five numbers.- What if the array has only one number?
The loop does not run.
best,worstandtotalall equalnums[0]. If it is positive,total - worstis 0 andbestwins. If it is not positive, the guard returnsbest. For[-5], the answer is -5.- How does Maximum Absolute Sum of Any Subarray (LC 1749) relate to this card?
It uses the same two Kadane passes without the total. The largest absolute sum is the larger of
bestand-worst. The array is not circular there, so no wrap-around case exists.
SWE Reasoning
Spot it
- The question asks for the best sum of one unbroken run of an array.
- The array is circular: the last number is followed by the first number.
- Plain Kadane's Algorithm fails on a small example such as
[5, -3, 5]. - The numbers can be negative, so the whole array is not always the best run.
- The answer is a sum or a similar value that can be computed as a total minus a middle part.
The invariant, step by step
- Before the loop, all five variables start at
nums[0]. Only one subarray ends at index 0. - For each
x,curr_maxandcurr_minapply the extend-or-restart rule withmaxand withmin. bestkeeps the largestcurr_maxandworstkeeps the smallestcurr_min.totaladdsx.- After the loop,
bestis the best plain run andworstis the worst plain run. - A run that wraps is
total - worst. A run that does not wrap isbest. - If
bestis not positive,total - worstmay be an empty run. In that case the function returnsbest.
Traps
- Missing guard. When every number is negative,
total - worstis 0, an empty run. For[-3, -2, -3], the result is 0 instead of -2. For[-5], it is 0 instead of -5. - Subtracting
bestand notworst. The wrapping run needs the smallest middle part removed. See the card's gotcha for[5, -3, 5]. - Plain Kadane only. It never joins the end to the start.
- Starting at 0. Starting
bestorworstat 0 allows an empty run. Start all variables atnums[0].
Practice
Builds on Kadane's Algorithm, which you haven’t finished yet.
Kadane's Algorithm on a Circular Array (Maximum Sum Circular Subarray)
min and keep the total.You get an integer array nums that is circular: the number after the last one is the first one again. A subarray is a run of numbers that sit next to each other, and it may wrap around the end.
Find the largest sum of a non-empty subarray. A subarray can use each position at most once.
[3] has the largest sum, 3.Circular Kadane
best plain run, or total - worst plain run ➔ Kadane twice, once with max and once with min ➔ guard the all-negative array| 1 | def max_subarray_circular(nums: list[int]) -> int: |
| 2 | """Maximum Sum Circular Subarray: the best plain run, or the total minus the worst plain run. |
| 3 | Time: O(N) | Space: O(1) |
| 4 | """ |
| 5 | # best, worst: the best and the worst sum of a plain subarray; curr_max, curr_min: the same for one that ends at the current number |
| 6 | total = curr_max = curr_min = best = worst = nums[0] |
| 7 | for x in nums[1:]: |
| 8 | # 1. Run Kadane For The Best And For The Worst: |
| 9 | # The same extend-or-restart rule, once with max for the best run and once with min for the worst run. |
| 10 | curr_max = max(x, curr_max + x) |
| 11 | curr_min = min(x, curr_min + x) |
| 12 | best = max(best, curr_max) |
| 13 | worst = min(worst, curr_min) |
| 14 | total += x |
| 15 | |
| 16 | # 2. Choose The Plain Run Or The Wrapping Run: |
| 17 | # A run that wraps around is the total minus a plain run in the middle, so remove the worst plain run. |
| 18 | # If every number is negative, the worst plain run is the whole array and the wrapping run would be empty, so keep best. |
| 19 | return max(best, total - worst) if best > 0 else best |
x, best and worst are the best and the worst sum of a plain subarray so far, and total is the sum of the numbers seen.A run in a circular array is a plain run, or it wraps around the end, and a wrapping run is the whole array without one plain run in the middle. So run Kadane's Algorithm twice, once with max for best and once with min for worst, keep total, and the answer is the larger of best and total - worst. The trap is an array of only negative numbers: worst is then the whole array, total - worst is 0, and that is an empty run, so return best when it is not positive.
See it run
Circular Kadane, step by step
curr_max -2curr_min -2best -2worst -2total -2
Each column is one index of nums. The rows curr_max and curr_min fill in as the steps run.
xthe number being readcurr_maxthe largest sum of a run that ends at xcurr_minthe smallest sum of a run that ends at x
Start: total, curr_max, curr_min, best and worst are all nums[0] = -2.
Gotcha 1 of 3
- The trap
Gotcha 1: Taking
total - worstwhen every number is negativeMistakereturn max(best, total - worst)Fixreturn max(best, total - worst) if best > 0 else bestWhat happens: When every number is negative, the worst plain run is the whole array, so
total - worstis 0. That would be an empty run, which is not allowed. The answer is the largest single number, whichbestalready holds.Example: Maximum Sum Circular Subarray (LC 918): nums = [-3, -2, -3]Mistake: returns 0Fix: returns -2
- The array is not circular, so a run never wraps around the end and one Kadane pass is enough, as in Maximum Subarray (LC 53). See Kadane's Algorithm
Deep Dive
Why a wrapping run is the total minus a plain run
In a circular array, the last number is followed by the first number. A run can start near the end and continue at the start. Such a run wraps around.
Place the array on a circle and choose a run. The numbers that are not in the run form one unbroken piece of the circle. If the run wraps around the end of the list, that remaining piece lies inside the list, in the middle. It is a plain subarray.
So the sum of a wrapping run is total minus the sum of a plain run in the middle. To make the wrapping run as large as possible, remove the plain run with the smallest sum. That sum is worst.
Example: for nums = [5, -3, 5], total is 7 and worst is -3. The wrapping run has the sum 7 - (-3) = 10. It is the two 5s that touch at the wrap. A plain pass finds only 7.
Two Kadane passes in one loop
Maximum Subarray (LC 53) taught the extend-or-restart rule with max. The same rule with min gives the smallest sum of a subarray that ends at x. The blueprint keeps curr_max and curr_min, and it updates best and worst from them.
Both passes use the same loop, so the time stays O(N). total adds one more number per step. The space is O(1), with five variables.
The answer is max(best, total - worst). best covers the runs that do not wrap. total - worst covers the runs that wrap.
Trace for nums = [5, -3, 5]: best is 7, worst is -3 and total is 7. The answer is max(7, 10) = 10.
The empty run and the guard
If the removed plain run is the whole array, then total - worst is 0. That is an empty run, and a run must hold at least one number.
This happens when every number is negative. Then worst is the whole array. The correct answer is the largest single number, which is best.
The guard if best > 0 else best handles it. If best is positive, an empty run (value 0) can never be larger than best, so max(best, total - worst) is safe. If best is 0 or negative, return best.
Example: for [-3, -2, -3], total is -8, worst is -8, and the answer is -2. For the single number [-5], the answer is -5.
Checks and a related problem
I compared max_subarray_circular with a brute force that tries every start and every length on the circle. They agreed on 10,000 random arrays of 1 to 9 numbers, including arrays of only negative numbers and arrays of one number.
Example with a longer wrap: nums = [-2, 4, -5, 4, -5, 9, 4] has best = 13, worst = -6 and total = 9. The answer is max(13, 9 + 6) = 15. The run is 9, 4, -2, 4, which wraps around the end.
Maximum Absolute Sum of Any Subarray (LC 1749) uses the same two passes. The answer is max(best, -worst), because the absolute value of the smallest sum can be the largest.
60-Second Pitch
"A run in a circular array either stays inside the list or wraps around the end. If it wraps, the numbers outside the run form one plain piece in the middle. So a wrapping run is total minus a plain subarray, and the largest one removes the smallest plain subarray. I run Kadane's Algorithm twice in one loop, with max for best and with min for worst, and I add up total. The answer is max(best, total - worst). If every number is negative, total - worst is an empty run, so I return best. The time is O(N) and the space is O(1)."
Common Questions
- Why does removing the smallest plain run give the best wrapping run?
A wrapping run is the whole circle without one plain run in the middle of the list. Its sum is
totalminus the sum of that plain run. To make the result as large as possible, remove the plain run with the smallest sum, which isworst.- What does the function return when every number is negative?
The largest single number, which is
best. Thenworstis the whole array, sototal - worstis 0. That would be an empty run, which is not allowed. For[-3, -2, -3], the answer is -2.- What is the time and space complexity?
O(N)time, because one loop runs both Kadane passes and the total.O(1)space, because the function keeps five numbers.- What if the array has only one number?
The loop does not run.
best,worstandtotalall equalnums[0]. If it is positive,total - worstis 0 andbestwins. If it is not positive, the guard returnsbest. For[-5], the answer is -5.- How does Maximum Absolute Sum of Any Subarray (LC 1749) relate to this card?
It uses the same two Kadane passes without the total. The largest absolute sum is the larger of
bestand-worst. The array is not circular there, so no wrap-around case exists.
SWE Reasoning
Spot it
- The question asks for the best sum of one unbroken run of an array.
- The array is circular: the last number is followed by the first number.
- Plain Kadane's Algorithm fails on a small example such as
[5, -3, 5]. - The numbers can be negative, so the whole array is not always the best run.
- The answer is a sum or a similar value that can be computed as a total minus a middle part.
The invariant, step by step
- Before the loop, all five variables start at
nums[0]. Only one subarray ends at index 0. - For each
x,curr_maxandcurr_minapply the extend-or-restart rule withmaxand withmin. bestkeeps the largestcurr_maxandworstkeeps the smallestcurr_min.totaladdsx.- After the loop,
bestis the best plain run andworstis the worst plain run. - A run that wraps is
total - worst. A run that does not wrap isbest. - If
bestis not positive,total - worstmay be an empty run. In that case the function returnsbest.
Traps
- Missing guard. When every number is negative,
total - worstis 0, an empty run. For[-3, -2, -3], the result is 0 instead of -2. For[-5], it is 0 instead of -5. - Subtracting
bestand notworst. The wrapping run needs the smallest middle part removed. See the card's gotcha for[5, -3, 5]. - Plain Kadane only. It never joins the end to the start.
- Starting at 0. Starting
bestorworstat 0 allows an empty run. Start all variables atnums[0].