Discussion 4: Recursive Methods
Solutions
Input Formatting:
It will be helpful to first format the input String s
before we start our recursive implementation. Think carefully on how to format the String
by looking at the examples above.
String s
to lowercase, by reassigning s = s.toLowerCase();
. This will make our palindrome computation case-insensitive.
Identify the Base Cases:
Inside the isPalindrome()
method, first check for the simplest possible inputs. When can we directly know that a String
is a palindrome without needing to do any work?
String
has length 0 or 1, it is automatically a palindrome.
Implement the Recursive Step:
If the input is not a base case, you must shrink the problem and call the method again on a smaller piece. What smaller String
should be the argument to this recursive call, and how do we obtain this smaller String
? How should we use the result of the recursive call to finish our definition of the method?
String
is the same as the last character (which would become the first character if the String
were reversed). After we confirm this, we need to check if the middle substring (after "peeling off" the first and last characters) is a palindrome. If it is, the entire String
is a palindrome. We can obtain this middle substring via the call s.substring(1,s.length()-1)
. We should return true
only when both checks (the outer characters and the middle substring) succeed, which we can accomplish with the boolean &&
operator.
Code up your Implementation:
|
|
|
|
String
of length \(N\), let's think about the time and space complexity of the isPalindrome()
method.
Describe the best-case and worst-case inputs of length \(N\). What properties will they have?
return false
before a recursive call is made. (Here, we're using the short-circuiting property of Java's &&
operator.)
For a String
of length \(N\), the worst case happens when we need to inspect every character before recursing to a base case. This occurs for any string that is a palindrome.
Choose a worst-case input of length 8 and draw a memory diagram that depicts the state of the runtime stack at the point just before returning from the base case.
Based on your diagram, what are the maximum call stack depth and total number of recursive calls, both expressed in terms of \(N\)? Give both exact answers and big-O complexity classes.
String
the total number of calls will be \(\lceil \frac{N+1}{2} \rceil = O(N)\).
Determine the runtime complexity of the non-recursive work done during the execution of the isPalindrome()
method on a String
of length \(k\). By summing this over all of the recursive calls in the stack, what is the overall time complexity of isPalindrome()
in terms of the original input length \(N\)?
String
's length and checking for (and executing the return
in) the base case are an \(O(1)\) operations. Converting the String
to lowercase is an \(O(k)\) operation. Comparing the first and last characters requires \(O(1)\) time. Finally, computing the substring for the recursive call argument requires \(O(k)\) time. Overall, this means the isPalindrome()
method does \(O(k)\) non-recursive work for an input of length \(k\). Summing this over the recursive calls gives overall time complexity \(O(N^2)\) for an original input of length \(N\) (by a similar calculation to the hasDuplicates()
method from lecture).
How much additional memory space is allocated during the execution of one call to isPalindrome()
on a String
of length \(k\)? By summing this over all of the recursive calls in the stack, what is the overall space complexity of isPalindrome()
?
String
and the substring in each method call uses an additional \(O(k)\) memory. Summing this over the recursive calls (and adding in the \(O(N)\) space from the call frames, which is dominated anyway) gives overall space complexity \(O(N^2)\) for an original input of length \(N\).
isPalindrome()
by adopting a similar strategy to the "array views" that we saw in lecture. Define a recursive helper method rangeIsPalindrome(String s, int start, int end)
that returns whether s.substring(start, end)
is a palindrome, and use this to re-implement isPalindrome()
(which should only handle the pre-processing and call this helper method), and analyze the time and space complexities of this alternate definition.
|
|
|
|