Notes on Maximum Subarray problem

Our treatment of this problem is taken from Chapter 8 of the book Programming Pearls, second edition, by Jon Bentley. The chapter and the book are wonderful to read, and I highly recommend them. The author provides a brief sketch of the chapter in the form of lecture notes in PDF format, as well as the source code for the algorithms (in C), together with a convenient driver.

The Science Library has one copy of the first edition of this book, which I have placed on reserve (this material appeared as Ch. 7 of that edition). I have also made several copies of Chapter 8 from my own copy of the second edition of the text, and those will hopefully be placed on reserve as well.

The problem is to take as input an array of n values (some of which may be negative), and to find a contiguous subarray which has the maximum sum. For example, consider the array:

31 -41 59 26 -53 58 97 -93 -23 84

We consider five distinct algorithms for solving this problem.


Algorithm 1:   A Cubic Algorithm (i.e., O(n3 ) )

Idea: For all pairs of integers, i ≤ j, check whether the sum of x[i..j] is greater than the maximum sum so far.
maxsofar = 0;
for (i = 0; i < n; i++)
    for (j = i; j < n; j++) {
        sum = 0;
        for (k = i; k <= j; k++)
            sum += x[k];
            if (sum > maxsofar)
                maxsofar = sum;
    }


Algorithm 2:   A Quadratic Algorithm (i.e., O(n2 ) )

Idea: The sum of x[i..j] can be efficiently calculated as (sum of x[i..j-1]) + x[j].
maxsofar = 0;
for (i = 0; i < n; i++) {
    sum = 0;
    for (j = i; j < n; j++) {
        sum += x[j];   // sum is that of x[i..j]
        if (sum > maxsofar)
            maxsofar = sum;
    }
}


Algorithm 2b:   Another Quadratic Algorithm (i.e., O(n2 ) )

Idea: Precalculate cumulative sums x[0..i] for all i. Then you can efficiently compute sum[a..b] = sum[0..b] - sum[0..a-1], when a>0.
maxsofar = 0;
cumarr[-1] = 0;
for (i = 0; i < n; i++)
    cumarr[i] = cumarr[i-1] + x[i];
for (i = 0; i < n; i++) {
    for (j = i; j < n; j++) {
        sum = cumarr[j] - cumarr[i-1];   // sum is that of x[i..j]
        if (sum > maxsofar)
            maxsofar = sum;
    }
}


Algorithm 3:   An O(n log n) Algorithm

Idea: Recursive divide and conquer. Find maximum solution for left half; find maximum solution for right half; find maximum solution which straddles the middle. One of those three will be the true optimal solution.
float recmax(int l, int u)
    if (l > u)  /* zero elements */
        return 0;
    if (l == u)  /* one element */
        return max(0, x[l]);
    m = (l+u) / 2;
    /* find max crossing to left */
    lmax = sum = 0;
    for (i = m; i >= l; i--) {
        sum += x[i];
        if (sum > lmax)
            lmax = sum;
    }
    /* find max crossing to right */
    rmax = sum = 0;
    for (i = m+1; i <= u; i++) {
        sum += x[i];
        if (sum > rmax)
            rmax = sum;
    }
    return max(lmax + rmax,
                max(recmax(l, m),
                recmax(m+1, u)));
}
The toplevel recursion is invoked as recmax(0,n-1).


Algorithm 4:   A Linear Algorithm (i.e., O(n) )

Idea: Do a simple scan, maintaining two values along the way, for index i:
  • "maxhere" : maximum subarray of x[0..i] of those ending precisely at i
  • "maxsofar" : maximum subarray of x[0..i]
  • maxsofar = 0
    maxendinghere = 0;
    for (i = 0; i < n; i++) {
        maxhere = max(maxhere + x[i], 0)
        maxsofar = max(maxsofar, maxhere)
    }
    


    Last modified: 14 January 2003