Kadane’s Algorithm: Working, Explanation, and Maximum Subarray Problem

kadane's algorithm and working of this algorithm

The first time I encountered the maximum subarray problem, I tried to solve it by checking every possible subarray, and I remember being frustrated at how slow that felt for large inputs. Kadane’s Algorithm was my introduction to the idea that dynamic programming doesn’t have to be complicated — sometimes it’s just a single running variable and a moment of insight. It finds the contiguous subarray with the largest sum in a one-dimensional array of numbers, and it does so in a single pass. I now think of it as one of the cleanest possible examples of dynamic programming thinking, and it’s usually the first “real” DP algorithm people learn.

History and Background

I credit this algorithm to Jay Kadane, a computer scientist who came up with the linear-time solution in 1984. What I find interesting is the backstory: the maximum subarray problem was originally posed by Ulf Grenander in the context of pattern recognition in digitized images, and it was popularized more broadly by a paper from Jon Bentley, who discussed multiple solutions of increasing sophistication (from cubic, to quadratic, to Kadane’s linear approach) in his “Programming Pearls” column. Bentley credited Kadane for the elegant linear solution after hearing about it, and the name has stuck ever since, even though Kadane himself didn’t publish it as a standalone paper.

Problem Statement

I am given an array of n integers, which may include negative numbers, and I need to find the contiguous subarray (containing at least one number) that has the largest possible sum. A brute-force approach checks all O(n^2) subarrays (or O(n^3) if I recompute each subarray’s sum from scratch), which becomes impractical for large arrays. Kadane’s algorithm solves this in a single linear pass by making a greedy-but-provably-optimal local decision at every position.

Core Concepts

How It Works

I maintain two variables as I scan the array left to right:

  1. current_max — the maximum sum of a subarray ending at the current index.
  2. global_max — the maximum sum found so far, across all positions.

For each element x in the array, I update:

By the time I finish scanning the array, global_max holds the answer.

Working Principle

The key realization that makes this work is that if current_max (the best subarray ending at the previous index) ever becomes negative, it can never help me — adding a negative number to any future subarray sum only hurts it. So whenever current_max + x < x, meaning the running sum has turned into a net negative contribution, I should discard it and restart from the current element. This greedy discarding is safe precisely because I’m not trying to remember every possible starting point — I only need the best sum ending at each index, and that value only depends on the best sum ending at the previous index, which is the essence of optimal substructure in dynamic programming.

Mathematical Foundation

Formally, I define:

$$ S(i) = \max(A[i],\ S(i-1) + A[i]) $$

where S(i) is the maximum subarray sum ending at index i, and A[i] is the array element at index i, with the base case S(0) = A[0].

The final answer is:

$$ \text{MaxSubarraySum} = \max_{0 \leq i < n} S(i) $$

This recurrence has optimal substructure: the optimal solution ending at i depends only on the optimal solution ending at i-1, and it has overlapping subproblems in the general dynamic programming sense, although in this specific case I don’t even need to store the full S array since each S(i) only depends on S(i-1).

Diagrams

flowchart TD
    A[Start: current_max = A0, global_max = A0] --> B[i = 1]
    B --> C{i < n?}
    C -- No --> H[Return global_max]
    C -- Yes --> D["current_max = max(A[i], current_max + A[i])"]
    D --> E["global_max = max(global_max, current_max)"]
    E --> F[i = i + 1]
    F --> C
graph LR
    subgraph Array values
    A1["-2"] --> A2["1"] --> A3["-3"] --> A4["4"] --> A5["-1"] --> A6["2"] --> A7["1"] --> A8["-5"] --> A9["4"]
    end
    style A4 fill:#9f6
    style A5 fill:#9f6
    style A6 fill:#9f6
    style A7 fill:#9f6

Pseudocode

function maxSubArray(A, n):
    current_max = A[0]
    global_max = A[0]
    for i from 1 to n - 1:
        current_max = max(A[i], current_max + A[i])
        global_max = max(global_max, current_max)
    return global_max

Step-by-Step Example

Consider the array: [-2, 1, -3, 4, -1, 2, 1, -5, 4]

I trace current_max and global_max at each index:

IndexValuecurrent_maxglobal_max
0-2-2-2
11max(1, -2+1) = 11
2-3max(-3, 1-3) = -21
34max(4, -2+4) = 44
4-1max(-1, 4-1) = 34
52max(2, 3+2) = 55
61max(1, 5+1) = 66
7-5max(-5, 6-5) = 16
84max(4, 1+4) = 56

The final answer is 6, coming from the subarray [4, -1, 2, 1].

Time Complexity

Space Complexity

Kadane’s algorithm uses O(1) extra space, since it only needs two scalar variables (current_max and global_max) regardless of the input array’s size. If I also want to recover the actual subarray (not just its sum), I need a few more scalar variables to track the start and end indices, which still keeps the space usage constant.

Correctness Analysis

I prove correctness by induction on the recurrence I defined earlier. The base case is trivially correct: the best subarray ending at index 0 is just A[0]. For the inductive step, assume S(i-1) correctly holds the maximum subarray sum ending at index i-1. Then any subarray ending at index i either consists of just A[i] alone, or it extends some subarray ending at i-1 by one more element. Since S(i-1) is by assumption the best possible sum for a subarray ending at i-1, extending it gives S(i-1) + A[i], and no other choice of subarray ending at i-1 could produce a larger extended sum. Taking the maximum of these two cases therefore correctly computes S(i). Since the global answer is the maximum of all S(i), and each S(i) is proven correct by induction, the algorithm as a whole is correct.

Advantages

Disadvantages

Applications

Implementation in C

#include <stdio.h>

/* Returns the maximum subarray sum and also records start/end indices */
int maxSubArray(int A[], int n, int *start, int *end) {
    int current_max = A[0];
    int global_max = A[0];
    int temp_start = 0;

    *start = 0;
    *end = 0;

    for (int i = 1; i < n; i++) {
        if (A[i] > current_max + A[i]) {
            /* Starting fresh at A[i] is better than extending */
            current_max = A[i];
            temp_start = i;
        } else {
            /* Extending the previous subarray is better */
            current_max = current_max + A[i];
        }

        if (current_max > global_max) {
            global_max = current_max;
            *start = temp_start;
            *end = i;
        }
    }

    return global_max;
}

int main() {
    int A[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int n = sizeof(A) / sizeof(A[0]);
    int start, end;

    int result = maxSubArray(A, n, &start, &end);

    printf("Maximum subarray sum is: %d\n", result);
    printf("Subarray is from index %d to %d: [", start, end);
    for (int i = start; i <= end; i++) {
        printf("%d", A[i]);
        if (i != end) printf(", ");
    }
    printf("]\n");

    return 0;
}

Sample Input and Output

Input:

A = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

Output:

Maximum subarray sum is: 6
Subarray is from index 3 to 6: [4, -1, 2, 1]

Optimization Techniques

Common Mistakes

Further Reading

Exit mobile version