CMPS 144
Recursive Solutions to Array and String problems: Three Examples

Problem 1: Array sum

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 sumSoFar = 0;
int i = 0;

// loop invariant: sumSoFar = sum of elements in a[0..i)
while (i != a.length) {
   sumSoFar = sumSoFar + a[i];
   i = i+1;
}
// assertion #1: sumSoFar = sum of elements in a[0..i) ∧ i = a.length
// assertion #2: sumSoFar = sum of elements in a[0..a.length)

return sumSoFar; 

Of course, assertion #1 is simply the conjunction of the loop invariant (which we believe to be true after every loop interation, including the last) and the negation of the loop guard (which we know to be true when the loop terminates). Assertion #2 follows from Assertion #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) ) = { 0   if n = 0
arraySum( a[0..n-1) ) + 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-1) ) + a[n-1]; }
      Bad syntax above   ^^^^^^^^^
   }  

Unfortunately, in Java there is no notation (such as a[0..n-1)) 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 the specified left boundary is inclusive and the specified right boundary is exclusive, which is to say that the array segment specified includes location low and extends up to, but not including, location high.

We get the following "auxiliary" method:

/* pre:  0 <= low <= high <= a.length
** post: value returned is the sum of the elements in a[low .. high)
*/
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)
*/
public static int arraySum(int[] a)
   { return arraySumAux(a, 0, a.length); }


Problem 2: String Reversal

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

Let c0c1c2...cn be a string, where each ci is a character. Then its reverse is cncn-1...c2c1c0

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 zero
a + reverse(s')   otherwise

where + denotes concatenation, s' denotes s with its last character chopped off, and a denotes the last character of s.

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) );
   }
}


Problem 3: Binary Search

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) < x ≤ a[k..N)
*/
public static int binSearch( int[] a, int x ) 

That is, the method, given an int x and ascending-ordered array a[] of int values, returns k such that every value in a[0..k) is less than x and every value in a[k..N) is greater than or equal to x. In picture form, we want

   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 the discussion of a 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) to be the remaining search space requires that a[0..low) < x and x ≤ a[high..N). 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 low (or, equivalently, high). Otherwise, we compute mid to be an integer halfway between 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) in ascending order &&
**       a[0..low) < x ≤ a[high..N)
** post: a[] is unchanged and value returned (call it k) satisfies 
**       a[0..k) < x ≤ a[k..N)
*/
public static int binSearchAux( int[] a, int low, int high, int x ) {

   if (low == high)
      { return low; }
   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) < x ≤ a[k..N)
*/
public static int binSearch( int[] a, int x )
   { return binSearchAux(a, 0, a.length, x); }