Skip to main content

918. Circular Subarray Sum

Quick Review​

Max Subarray Sum 👈 This is a classic bottom up DP problem. It can be solved with Kadane's algorithm in O(n)O(n) time and O(1)O(1) space.

GeeksforGeeks explanation of Kadane's algorithm

☝️ If you aren't yet familiar with Kadane's algorithm, read this article first.

Once you've got problem 53, come tackle this trickier problem 918.

Circular Subarray Sum​

Problem 53 only allows us to find two indices ii and jj in an array numsnums of length nn,

satisfying 0≤i≤j<n0 \leq i \leq j < n, to maximize sum(nums[i:j+1])\text{sum}(nums[i: j + 1]).

Problem 918 accepts the same type of subarray as problem 53,

but it also accepts circular subarrays: find two indices ii and jj satisfying 0≤i≤j<n0 \leq i \leq j < n,

maximizing sum(nums[:i]+nums[j:])\text{sum}(nums[:i] + nums[j:])

In short, this means taking a prefix and a suffix from numsnums, with the constraint that no indices can be used twice.

The Naive Wrong Path 🫨​

To be honest, when I first solved problem 53,

I tried the "array concatenation" approach —

concatenating numsnums into nums+numsnums + nums and applying problem 53's logic directly.

That ended with five wrong answers over two and a half months 🤣

Time for Reverse Thinking 😌​

Almost three months later, one day outside my home, there was construction going on 🚧 with an excavator digging into the ground 🪏

Watching the excavator scoop downward, I suddenly realized that:

problem 918 requires "excavation"!

That is, find the subarray with the most negative sum in numsnums,

subtract it from sum(nums)\text{sum}(nums), and apply "addition by subtraction" to eliminate this deficit — what remains is the maximum circular subarray sum 😁

Kadane's algorithm in problem 53 finds the maximum subarray,

and here we need to find the subarray with the minimum negative sum,

so we just negate it: a reversal of thinking.

The Excavator Can't Dig Recklessly​

Just as construction must avoid hitting water pipes or power lines,

we must make sure we don't "dig out everything".

What do I mean? Consider the case where all elements in numsnums are non-positive.

The most negative subarray would simply be sum(nums)\text{sum}(nums),

and subtracting it from sum(nums)\text{sum}(nums) leaves an empty subarray —

an empty subarray is not allowed.

How shall we fix things? Simply track max(nums)max(nums):

I. If max(nums)≤0max(nums) \leq 0, every element has no positive contribution.

In this case, pick the element with the least poison — that's max(nums)max(nums).

II. When max(nums)>0max(nums) > 0, the answer is one of:

  1. A standard maximum subarray as in problem 53.
  2. A circular subarray: sum(nums)\text{sum}(nums) minus the most negative subarray.

Take the larger of these two.

So we have some key variables to track:

  1. maxPosSum: maximum positive subarray sum
  2. minNegSum: minimum negative subarray sum
  3. arrayTotalSum: sum(nums)\text{sum}(nums)
  4. maxNum: max(nums)max(nums)

The overall logic still follows problem 53's spirit.

#include <vector>
using namespace std;

int computeMaxCircularSubarraySum(vector<int>& nums) {
int arrayTotalSum = 0;
int maxNum = nums.front();

int posSubarraySum = 0, maxPosSum = 0;

int negSubarraySum = 0, minNegSum = 0;

for (const auto& num : nums) {
if (num > maxNum)
maxNum = num;

arrayTotalSum += num;

posSubarraySum += num;
if (posSubarraySum > maxPosSum)
maxPosSum = posSubarraySum;

if (posSubarraySum <= 0) // Positive sum <= 0: reset subarray.
posSubarraySum = 0;

negSubarraySum += num;
if (negSubarraySum < minNegSum)
minNegSum = negSubarraySum;

if (negSubarraySum >= 0) // Negative sum >= 0: reset subarray.
negSubarraySum = 0;
}

if (maxPosSum == 0) // All numbers are non-positive.
return maxNum;

return max(arrayTotalSum - minNegSum, maxPosSum);
}

DP_Efficiency A fresh perspective lets us solve this quirky problem in O(n)O(n) time and O(1)O(1) space.

Though sometimes it takes a long while before the right perspective finally arrives 😹