##
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.

I offer my own Python implementation and test harness.

This problem is also introduced in Chapter 4.1 of CLRS, with a
presentation of the O(n log n) divide-and-conquer
algorithm. (The linear time algorithm is the subject of Exercise 4.1-5.)

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 |

I will adopt a mix of notations from the two book, using `A` as
the notation for the array, and assuming it is indexed from `0`
to `n-1`.
We consider five distinct algorithms for solving this problem.

### Algorithm 1: A Cubic Algorithm (i.e., O(n^{3} ) )

*Idea:* For all pairs of integers, i ≤ j, check whether the sum of
A[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 += A[k];
if (sum > maxsofar)
maxsofar = sum;
}

### Algorithm 2: A Quadratic Algorithm (i.e., O(n^{2} ) )

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

### Algorithm 2b: Another Quadratic Algorithm (i.e., O(n^{2} ) )

*Idea:* Precalculate cumulative sums A[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] + A[i];
for (i = 0; i < n; i++) {
for (j = i; j < n; j++) {
sum = cumarr[j] - cumarr[i-1]; // sum is that of A[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, A[l]);
m = (l+u) / 2;
/* find max crossing to left */
lmax = sum = 0;
for (i = m; i ≥ l; i--) {
sum += A[i];
if (sum > lmax)
lmax = sum;
}
/* find max crossing to right */
rmax = sum = 0;
for (i = m+1; i ≤ u; i++) {
sum += A[i];
if (sum > rmax)
rmax = sum;
}
return max(max(recmax(l, m),
recmax(m+1, u)),
lmax + rmax);
}

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*:
maxsofar = 0
maxendinghere = 0;
for (i = 0; i < n; i++) {
maxhere = max(maxhere + A[i], 0)
maxsofar = max(maxsofar, maxhere)
}

Last modified: Tuesday, 28 August 2012