CMPS 144 Intersession 2020
Prog. Assg. #5: Maximum Segment Sum, Recursively

Background

If a[] is an array with length N (i.e., N = a.length), then, for any pair of integers (k,m) satisfying 0≤k≤m≤N, a[k..m) refers to the portion of a[] beginning at location k and extending up to, but not including, location m. We refer to this as an array segment. A special case of an array segment is the whole array: take k to be 0 and m to be N. Another special case is an empty segment: take k and m to be equal. (Indeed, one can interpret it to be that each of a[0..0), a[1..1), ..., a[N..N) is a distinct empty segment of a!)

Of course, an array segment has subsegments. Specifically, if a[k..m) is an array segment, then, for any pair (k',m') satisfying k≤k'≤m'≤m, a[k'..m') is a subsegment of a[k..m). A special case of a subsegment is a prefix: a[k'..m') is a prefix of a[k..m) if k = k' ≤ m' ≤ m. An analogous special case is a suffix: a[k'..m') is a suffix of a[k..m) if k ≤ k' ≤ m' = m.

Now, if a[k..m) is a segment of an array that contains values of a numeric type (e.g., int, double, or even Integer), it makes sense to refer to its sum, meaning the sum of the elements that it contains: a[k] + a[k+1] + ... + a[m−1].

The Maximum Segment Sum problem is this: Given an array segment (or, as a special case, an entire array), determine the maximum among the sums of all its subsegments.

For example, consider the array a[] pictured below. Among all its segments, the largest of their sums is 16. Indeed, there are four segments having that sum: a[2..8), a[5..8), a[11..16), and a[18..22).

0123456 7 8910111213 14151617181920 2122232425
3−851 −612−59 −2−10−81 25−210 −7−11210 −59−135 2−3


Your Task

Given are the following Java classes:

The student's task is to complete the stubbed methods in the MaxSegSumViaRec() class.1 The most significant of these is maxSegSum(), which is to be based upon these observations, giving rise to a recursive algorithm:

Referring to our example array, repeated below, note that two of the segments with maximum sum are in the "left" half of the array (namely, a[2..8] and a[5..8)), one is in the "right" half (namely, a[18..22)), and one (a[11..16)) is a spanning segment.

0123456 7 8910111213 14151617181920 2122232425
3−851 −612−59 −2−10−81 25−210 −7−11210 −59−135 2−3

The method measureOfWork() is intended to provide a quantity that describes how much computation was carried out by the most recent call(s) to the maxSegSum() method. (The reset() method sets this measure back to zero.) A good way of doing this is to charge one unit of work for every call to a recursive method and one unit of work to each iteration of a loop.

The student should use the given tester program not only to determine whether the results of computing maximum segment sums are correct but also to get a sense of the run-time complexity of the algorithm. To do that, perform tests using arrays of increasing lengths and see how the measure of work increases as a function of that length. Start with some relatively small array length, like 20, and then keep doubling it, to 40, 80, 160, etc. If your implementation is correct, the amount of work should slightly more than double each time you double the length of the array. This is characteristic of an algorithm whose runtime complexity is O(n·log n). Note that the algorithm in the MaxSegSumViaPrefixSums class runs in O(n) time, which means that the amount of work that it does doubles (or very close to it) each time the array length doubles.


Footnote

[1] Two of the stubbed methods, maxPrefixSum() and maxSuffixSum(), are private, and thus are intended to play only supporting roles. Strictly speaking, then, they are not required. However, the student is strongly encouraged to complete and make use of them.

For that matter, each of those two methods has a natural recursive solution, and the student is encouraged (but not required) to employ it. Take maxPrefixSum(), which computes the maximum among the sums of the prefixes of b[bottom..top). Of course, if bottom = top (the base case), the only prefix is empty and thus has sum zero. Assuming that bottom < top (the recursive case), the maximum among the sums of the prefixes of b[bottom..top) is either

whichever is the larger. That might strike you as odd, but consider the example of a[22..26) from our example array, which contains the values −13, 5, 2, and -3, in that order. The maximum prefix sum of a[23..26) is 7, which, when added to a[22] (−13), yields a negative number. Hence, the maximum prefix sum of a[22..26) is zero, reflecting the fact that, among all prefixes of a[22..26), the empty prefix a[22..22) has the maximum sum.