Recursive solutions to array problems: Three examples

Develop a Java method that, given an array `a[]` of integers,
computes the sum of the elements in `a[]`.
(Note that, in Java, the index range of an array `a` is
`0..a.length-1`. We use `a[]` as an abbreviation
for `a[0..a.length-1]`.)
The specification is as follows:

```
/* pre: none
post: value returned is sum of elements in a[]
*/
public static int arraySum(int[] a) { ... }
```

An obvious iterative solution (to replace "..." in the specification above)
is as follows:

```
int sum = 0;
// loop invariant: sum = sum of elements in a[0..i-1]
for (int i=0; i != a.length; i++) {
sum = sum + a[i];
}
// assertion 1: i = a.length & sum = sum of elements in a[0..i-1]
// assertion 2: sum = sum of elements in a[0..a.length-1]
return sum;
```

Of course, assertion 1 is simply the conjunction of the negation of the loop guard and the loop invariant. Assertion 2 follows from 1 and tells us that the next statement returns the correct value to the caller.

Let us now consider how we might solve this problem recursively. One idea is this: the sum of the elements in an empty array is zero. The sum of the elements in a nonempty array can be obtained by adding the last element to the sum of all the previous elements.

To state it a little more precisely: for any natural number `n`
not greater than `a.length` we have

arraySum( a[0..n-1] ) = { 0 if n=0 { arraySum( a[0..n-2] ) + a[n-1] otherwise

This suggests the following recursive solution:

```
public static int arraySum(int[] a) {
int n = a.length;
if (n == 0)
{ return 0; }
else
{ return arraySum( a[0..n-2] ) + a[n-1]; }
}
```

Unfortunately, in Java there is no notation (such as `a[0..n-2]`)
by which to refer to a segment of an array.
Thus, **the above is not syntactically correct**.
To get the effect of passing an array segment as a parameter, we let
the method have three parameters: the array itself together with
`int` parameters `low` and `high` indicating,
respectively, the left and right boundaries of the segment of interest.
(For reasons of convenience, we use the convention that `high - 1`,
rather than `high` itself, is the location of the last element in
the segment.)
We get the following "auxiliary" method:

```
/* pre: 0 <= low <= high <= a.length
post: value returned is sum of elements in a[low .. high-1]
*/
public static int arraySumAux(int[] a, int low, int high) {
if (low == high)
{ return 0; }
else // low < high
{ return arraySumAux(a, low, high-1) + a[high-1]; }
}
```

Now we can write the original method as follows:

```
/* pre: none
post: value returned is sum of elements in a[0..a.length-1]
*/
public static int arraySum(int[] a)
{ return arraySumAux(a, 0, a.length); }
```

Develop a Java method that, given a String `s`, returns the reverse
of `s`.

Let
c_{0}c_{1}c_{2}...c_{n}
be a string, where each c_{i} is a character.
Then its reverse is
c_{n}c_{n-1}...c_{2}c_{1}c_{0}

Notice that the first character of the reverse is the last character of the original and that what follows it is the reverse of the string obtained by chopping off the last character of the original string. Combining this with the observation that the reverse of the empty string is itself, we surmise that

reverse(s) = { s if s has length 0 { a + reverse(s') otherwise where+denotes concatenation,s'denotesswith its last character chopped off, andadenotes the last character ofs.

Indeed, this recursive definition of reverse corresponds to our intended meaning. We shall not prove this here, but, just for illustration, we demonstrate the application of the function to the string "abcde":

reverse("abcde") = 'e' + reverse("abcd") = 'e' + ('d' + reverse("abc")) = 'e' + ('d' + ('c' + reverse("ab"))) = 'e' + ('d' + ('c' + ('b' + reverse("a")))) = 'e' + ('d' + ('c' + ('b' + ('a' + reverse(""))))) = 'e' + ('d' + ('c' + ('b' + ('a' + "")))) = 'e' + ('d' + ('c' + ('b' + "a"))) = 'e' + ('d' + ('c' + "ba")) = 'e' + ('d' + "cba") = 'e' + "dcba" = "edcba"

Each of the first five lines follows from the recursive case of the definition of reverse; the sixth line follows from the base case; the remaining lines follow from the meaning of concatenation.

In order to express the above in Java, we must be able to refer to the last
character in a String object `s` as well as to the string obtained
by chopping off the last character in a String object `s`.
Assuming that `n` is the length of `s`, these are given by
the expressions `s.charAt(n-1)` and `s.substring(0, n-1)`.
We must also be able to express concatenation. But Java gives us this with
the `+` operator (which is why we chose to use it above).
This leads to the following method:

```
/* pre: none
post: object returned is a String that is the reverse of s
*/
public static String reverseOf( String s ) {
if (s.length == 0)
{ return s; }
else {
int n = s.length;
return s.charAt(n-1) + reverseOf( s.substring(0, n-1) );
}
}
```

Using `N` as an abbreviation for `a.length` and
`a[m..n] < x` as an abbreviation for "`a[i] < x`
for all `i` satisfying `m<=i<=n`"
(and similarly for >= in place of <),
develop a Java method with the following specification:

```
/* pre: elements in a[] are in ascending order
post: a[] is unchanged and the value returned (call it k) satisfies
0 <= k <= N &&
a[0..k-1] < x &&
a[k..N-1] >= x,
*/
public static int binSearch( int[] a, int x )
```

That is, the method, given an 0 k N +---------------------+-----------------------+ a | all < x | all >= x | +---------------------+-----------------------+

*Idea:* Due to the fact that the elements of `a[]` are in
ascending order, we can search for `x` using the classic
**binary search** method discussed in class, in which we
successively halve the search space until it has been reduced to one
so small that the result can be computed directly
(i.e., without further halving).

As we observed in Problem 1, there is no expression in Java by which
to refer to an array segment.
Hence, we introduce an auxiliary method with `int` parameters
`low` and `high` (in addition to the array parameter) to
indicate the boundaries of the array segment serving as the
remaining search space. Let us agree that for `a[low..high-1]`
to be the remaining search space requires that `a[0..low-1] < x`
and `a[high..N-1] >= x`. In picture form:

0 low high N +-------------+---------------+--------------+ a | all < x | ? | all >= x | +-------------+---------------+--------------+

If `low == high`, the ?-section of the array is empty, so
the desired result is `high` (or, equivalently, `low`).
Otherwise, we compute `mid` to be the average of `low` and
`high` and then determine in which of the two segments,
`a[low..mid]` or `a[mid+1..high]`, the correct answer must lie.
Either way, the answer may be obtained recursively, because what remains
to be done is to search the appropriate segment:

```
/* pre: Letting N = a.length,
0 <= low <= high <= N &&
a[0..N-1] in ascending order &&
a[0..low-1] < x &&
a[high..N-1] >= x
post: a[] is unchanged and value returned (call it k) satisfies
a[0..k-1] < x && a[k..N-1] >= x
*/
public static int binSearchAux( int[] a, int low, int high, int x ) {
if (low == high)
{ return high; }
else {
int mid = (low + high) / 2;
// assertion: low <= mid < high
if ( a[mid] >= x )
{ return binSearchAux(a, low, mid, x); }
else // a[mid] < x
{ return binSearchAux(a, mid+1, high, x); }
}
}
```

The body of the original method, whose purpose was to search an entire array,
can be written as an invocation of the above auxiliary method:

```
/* pre: elements in a[] are in ascending order
post: returns k satisfying a[0..k-1] < x && a[k..N-1] >= x
*/
public static int binSearch( int[] a, int x )
{ return binSearchAux(a, 0, a.length, x); }
```