Back to Arrays
Q91Question 91 of 109

Maximum Subarray (Kadane's Algorithm): running sum reset at negatives

Key Concepts
  • Kadane's algorithm finds the maximum subarray sum in a single O(n) pass with O(1) space.
  • At each element, choose the larger of the element alone or the element plus the running sum: current = max(num, current + num).
  • Choosing the element alone discards a negative prefix that would drag the sum down.
  • Initialize both current and answer to the first element, not zero, so all-negative arrays return the largest single element.
  • Tracking start and end indices alongside the sums recovers the actual subarray boundaries.
  • A divide-and-conquer alternative exists in O(n log n) but Kadane's linear scan is strictly better.

Definition

current = max(num, current + num); answer = max(answer, current) : extend or restart each step.
  • Kadane's algorithm – a linear dynamic programming scan that maintains the best subarray sum ending at the current index.
  • Decision at each step – current = max(num, current + num) either extends the running subarray or restarts from the current element.
  • Global tracking – answer = max(answer, current) updates the best sum seen so far.
  • Initialization – both current and answer start at the first element, not zero, to handle all-negative arrays correctly.
  • Complexity – O(n) time, O(1) space.

At the National Bank of India, Pankaj scans daily net cash flows for a branch. He tracks the best streak ending today: either extend yesterday's streak by adding today's amount, or start fresh today if the past streak is dragging him down. The best streak he has ever seen is his answer for the most profitable contiguous run.

Understanding the Concepts

What is the Maximum Subarray problem?

Given an integer array, find the contiguous subarray with the largest sum and return that sum. For example, in [-2, 1, -3, 4, -1, 2, 1, -5, 4], the maximum subarray is [4, -1, 2, 1] with sum 6.

At the National Bank of India, analysts review daily net cash flows and need to find the most profitable contiguous window of days.

How does Kadane's algorithm work?

Kadane's algorithm scans the array once, maintaining two values:

  • current: the maximum subarray sum ending at the current index.
  • best: the maximum sum seen so far across all indices.

At each element, the decision is: should we extend the previous subarray, or start a fresh one here?

JAVA
current = Math.max(num, current + num);
best = Math.max(best, current);

If current + num is less than num alone, the running sum was negative and dragging the total down, so we discard it and start fresh.

JAVA
package com.bank.analytics;

public class MaxSubarraySum {
    public static int kadane(int[] cashFlows) {
        int current = cashFlows[0];
        int best = cashFlows[0];

        for (int i = 1; i < cashFlows.length; i++) {
            current = Math.max(cashFlows[i], current + cashFlows[i]);
            best = Math.max(best, current);
        }
        return best;
    }

    public static void main(String[] args) {
        int[] dailyFlows = {-200, 150, -50, 400, -100, 250, 100, -500, 300};
        System.out.println("Maximum subarray sum: " + kadane(dailyFlows));
    }
}
TEXT
Maximum subarray sum: 750

Why initialize to the first element and not zero?

If all elements are negative, initializing to zero would return zero, which is wrong because zero is not a valid subarray sum. Initializing to the first element ensures the answer is the largest single element in an all-negative array.

JAVA
// Wrong: initializing to zero
int current = 0, best = 0;
// For [-5, -3, -1], returns 0 instead of -1

// Correct: initializing to first element
int current = nums[0], best = nums[0];
// For [-5, -3, -1], correctly returns -1

How to recover the actual subarray indices?

To find not just the sum but the actual subarray, track candidate start and end indices.

JAVA
package com.bank.analytics;

public class MaxSubarrayWithIndices {
    public static void findMaxSubarray(int[] cashFlows) {
        int current = cashFlows[0];
        int best = cashFlows[0];
        int start = 0, end = 0, tempStart = 0;

        for (int i = 1; i < cashFlows.length; i++) {
            if (cashFlows[i] > current + cashFlows[i]) {
                current = cashFlows[i];
                tempStart = i;
            } else {
                current = current + cashFlows[i];
            }
            if (current > best) {
                best = current;
                start = tempStart;
                end = i;
            }
        }
        System.out.println("Max sum: " + best);
        System.out.println("From day " + start + " to day " + end);
    }

    public static void main(String[] args) {
        int[] dailyFlows = {-200, 150, -50, 400, -100, 250, 100, -500, 300};
        findMaxSubarray(dailyFlows);
    }
}
TEXT
Max sum: 750
From day 1 to day 6

What is the time and space complexity?

| Approach | Time | Space |
|---|---|---|
| Kadane's algorithm | O(n) | O(1) |
| Brute force (all subarrays) | O(n^2) | O(1) |
| Divide and conquer | O(n log n) | O(log n) |

Kadane's algorithm is optimal. No algorithm can do better than O(n) because every element must be examined at least once.

How does it compare to the brute-force approach?

The brute force checks every possible subarray and computes its sum, which is O(n^2). Kadane's key insight is that the maximum subarray ending at position i depends only on the maximum subarray ending at position i-1, making it a simple dynamic programming recurrence.

JAVA
// Brute force: O(n^2)
for (int i = 0; i < n; i++) {
    int sum = 0;
    for (int j = i; j < n; j++) {
        sum += nums[j];
        best = Math.max(best, sum);
    }
}

// Kadane: O(n)
for (int i = 1; i < n; i++) {
    current = Math.max(nums[i], current + nums[i]);
    best = Math.max(best, current);
}

What are the edge cases?

  • Single element: return that element.
  • All negative: return the largest (least negative) element.
  • All positive: the entire array is the maximum subarray.
  • Contains zeros: zeros neither help nor hurt; the algorithm handles them naturally.
JAVA
int[] single   = {42};           // result: 42
int[] allNeg   = {-5, -3, -1};   // result: -1
int[] allPos   = {1, 2, 3, 4};   // result: 10
int[] withZero = {-1, 0, -2};    // result: 0

What are common mistakes?

The most common mistake is initializing current and best to zero instead of the first element. Another mistake is forgetting to update best after every step.

JAVA
// Wrong: best updated only at the end
for (int i = 1; i < n; i++) {
    current = Math.max(nums[i], current + nums[i]);
}
best = current; // misses peaks that occurred earlier

// Correct: best updated inside the loop
for (int i = 1; i < n; i++) {
    current = Math.max(nums[i], current + nums[i]);
    best = Math.max(best, current);
}

What should a senior engineer know?

  • Variant: Maximum Subarray Product requires tracking both max and min because a negative times a negative becomes positive.
  • Variant: Circular Maximum Subarray uses the identity that the circular max is the total sum minus the minimum subarray sum.
  • Divide and Conquer splits the array in half, recurses on both sides, and combines by extending from the midpoint. It is O(n log n) and useful for educational purposes but not practical.
  • Prefix sum approach computes prefix sums and finds the max difference between any later prefix sum and an earlier one, which is equivalent to Kadane's.

What are common interview follow-up questions?

  • Return the actual subarray, not just the sum. (Track indices.)
  • What if the array is circular? (Total minus minimum subarray.)
  • What if you must pick at least k elements? (Combine prefix sums with a sliding window.)
  • Maximum product subarray? (Track both max and min products.)

Visual Overview

TEXT
Array:   [-200, 150, -50, 400, -100, 250, 100, -500, 300]
Index:      0    1    2    3     4    5    6     7    8

Step 0: current = -200, best = -200
Step 1: max(150, -200+150) = max(150, -50) = 150   best = 150
Step 2: max(-50, 150-50) = max(-50, 100) = 100     best = 150
Step 3: max(400, 100+400) = max(400, 500) = 500    best = 500
Step 4: max(-100, 500-100) = max(-100, 400) = 400  best = 500
Step 5: max(250, 400+250) = max(250, 650) = 650    best = 650
Step 6: max(100, 650+100) = max(100, 750) = 750    best = 750  <-- answer
Step 7: max(-500, 750-500) = max(-500, 250) = 250  best = 750
Step 8: max(300, 250+300) = max(300, 550) = 550    best = 750

Maximum subarray sum = 750 (days 1 through 6)
DIAGRAM
┌──────────────────────────────────────────┐
│         Kadane's Decision at Each Step    │
├──────────────────────────────────────────┤
│                                          │
│   At each element num:                   │
│       │                                  │
│       ▼                                  │
│   ┌────────────────────────────┐         │
│   │ current + num  vs  num     │         │
│   └────────────────────────────┘         │
│       │                                  │
│   current + num >= num?                  │
│   Yes: EXTEND  │  No: RESTART            │
│       │              │                   │
│       └──────┬───────┘                   │
│              ▼                           │
│   best = max(best, current)              │
└──────────────────────────────────────────┘

Key Takeaway

Maximum Subarray is solved by Kadane's algorithm in a single linear pass. You keep two values: the best subarray sum ending exactly at the current position, and the best sum seen anywhere so far. At each element you set the ending-here sum to the larger of the element alone or the previous ending-here sum plus the element, which is the decision to either start a fresh subarray or extend the existing one. Then you update the global best.

Restarting with the element alone is what discards a harmful negative prefix. Any prefix that dragged the running sum below starting fresh can never be part of an optimal subarray ending at this position. Initializing both values to the first element, rather than zero, is the detail that makes all-negative arrays return the largest single element correctly.

In the analytics team at the National Bank of India, Pankaj needed the most profitable contiguous run of daily net cash flows to pinpoint a peak performance window. Kadane's algorithm computed it in one pass over the ledger. When the team needed the actual window and not just its total, Rajesh tracked the candidate start and end indices alongside the sums to recover the exact span.

The senior-level extension is recognizing variants. Maximum Subarray Product requires tracking both max and min because negatives flip signs. Circular Maximum Subarray uses the total minus the minimum subarray sum. These build directly on the Kadane foundation.

Remember one sentence: at each element, choose to extend or restart, and the global best across all choices is the answer.

---

Share
Use ← → arrow keys to navigate