This is a problem from my Introduction to Algorithms textbook.
Consider the infinite sequence of numbers An|n>0 = (1,1,3,4,8,11,...) defined recursively as follows.
$$ A_n = \left\{\begin{aligned} &1 && n = 1,2\\ &B_{n-1}+A_{n-2} && n > 2 \end{aligned} \right. \\ B_n = \left\{\begin{aligned} &2 && n = 1,2\\ &A_{n-1}+B_{n-2} && n > 2 \end{aligned} \right. $$
- Write a recursive method to compute the nth value of A, for any given integer n.
- Write a “bottom-up” method to compute the nth value of A, for any given integer n.
I wrote a solution for each but found myself using auxiliary methods in both cases. A recent criticism comes to mind and I wonder if this is unnecessarily complex. Could it be simpler?
Of course any feedback on the efficacy of my approach is welcomed.
My recursive algorithm, stratified into two methods:
private static int computeAuxRecursive(int n) { if (n <= 0) { throw new IllegalArgumentException("n mustbe an integer larger than 0"); } return n <= 2 ? 2 : computeNthRecursive(n - 1) + computeAuxRecursive(n - 2); } private static int computeNthRecursive(int n) { if (n <= 0) { throw new IllegalArgumentException("n must be an integer larger than 0"); } return n <= 2 ? 1 : computeAuxRecursive(n - 1) + computeNthRecursive(n - 2); }
Convenient testing method:
private static String retrieveSequenceRecursive(int length) { StringBuilder result = new StringBuilder(); for (int i = 1; i <= length; i++) { result.append(", ").append(computeNthRecursive(i)); } return result.substring(2); }
For my bottom-up algorithm I elected to go with a map:
private static Map<Integer, int[]> memoizedMap = new HashMap<Integer, int[]>();
Pre-filled, according to specification, like so:
memoizedMap.put(1, new int[]{1, 2}); memoizedMap.put(2, new int[]{1, 2});
The idea is to use the Nth position requested as a key, and the array of length 2 to hold both the A and B value, using a flag to determine which is being requested.
Actual two methods used for Bottom up algorithm:
private static int computeNthBottomUp(int n) { if (n <= 0) { throw new IllegalArgumentException("n must be an integer larger than 0"); } if (memoizedMap.containsKey(n)) { return memoizedMap.get(n)[0]; } memoizedMap.put(n, new int[]{ bottomUpHelper(n, false), bottomUpHelper(n, true) }); return memoizedMap.get(n)[0]; } // flag boolean to check if we want the B value or not private static int bottomUpHelper(int n, boolean bFlag) { if (memoizedMap.containsKey(n)) { return bFlag ? memoizedMap.get(n)[1] : memoizedMap.get(n)[0]; } memoizedMap.put(n, new int[]{ bottomUpHelper(n - 2, false) + bottomUpHelper(n - 1, true), bottomUpHelper(n - 2, true) + bottomUpHelper(n - 1, false) } ); return bFlag ? memoizedMap.get(n)[1] : memoizedMap.get(n)[0]; }
Another convenience method:
private static String retrieveSequenceBottomUp(int begin, int end) { StringBuilder result = new StringBuilder(); for (int i = begin; i <= end; i++) { result.append(", ").append(computeNthBottomUp(i)); } return result.substring(2); }
Sample tests:
@Test public void testTenthInSequence() { assertEquals(76, computeNthRecursive(10)); assertEquals(76, computeNthBottomUp(10)); } @Test public void testfirstTenRecursive() { assertEquals("1, 1, 3, 4, 8, 11, 21, 29, 55, 76", retrieveSequenceRecursive(10)); assertEquals("1, 1, 3, 4, 8, 11, 21, 29, 55, 76", retrieveSequenceBottomUp(1, 10)); }