Chapter 4: Recursion

Chapter 4: Recursion · Study Questions
Base caseThe call stackLinear recursionBinary recursionHanoi and backtrackingRecurrence relationsTail recursionRecursion on linked lists
Sercan KÜLCÜ · Giresun University · Computer Engineering

Multiple Choice · What Is Recursion

Question 1: Termination

What guarantees that a recursive method terminates on every input?

B: The base case stops the recursion, and the recursive step must bring the input closer to it on every call (n − 1, n / 2 …). Neither the number of calls nor the return type decides termination, and a growing parameter never reaches the base case.

Short Answer · What Is Recursion

Question 2: Printing Before and After

What does f(3) print? Explain the order of the prints before and after the call.

void f(int n) {
  if (n == 0) return;
  System.out.print(n + " ");
  f(n - 1);
  System.out.print(n + " ");
}
Show answer

Output: 3 2 1 1 2 3

  • Line 3 runs before the call, printing 3, 2, 1 as the calls go down.
  • f(0) is the base case and prints nothing.
  • Line 5 runs after the call: as the frames are popped in reverse order, f(1), f(2), f(3) print 1, 2, 3.

Code Trace · The Call Stack

Question 3: Printing on the Way Back

What does printBinary(6) print, and what is the largest number of frames (including main) on the call stack?

main calls printBinary(6).

n = 6 ≠ 0 → line 3 calls printBinary(3); the print waits.

New frame with n = 3 → call printBinary(1).

n = 1 → 1 / 2 = 0: call printBinary(0).

Base case: return without printing. The stack is at its deepest: 5 frames.

printBinary(1) resumes where it left off: prints 1 % 2 = 1.

printBinary(3): prints 3 % 2 = 1.

printBinary(6): prints 6 % 2 = 0.

Output 110 (6 in binary); at most 5 frames. n halves with every call → O(log n).

void printBinary(int n) {
  if (n == 0) return;
  printBinary(n / 2);
  System.out.print(n % 2);
}
// main:
printBinary(6);

Multiple Choice · The Call Stack

Question 4: An Unreachable Base Case

What do f(5) and f(6) produce?

int f(int n) {
  if (n == 1) return 1;
  return n * f(n - 2);
}

B: f(5) = 5 · f(3) = 5 · 3 · f(1) = 15. f(6) goes 6, 4, 2, 0, −2 …; n never becomes 1, so the call stack fills up. With the base case n <= 1, f(6) would be 6 · 4 · 2 · 1 = 48.

Short Answer · The Call Stack

Question 5: Stack Depth

What does h(20) return? How many calls are made, and what is the largest number of frames (including main) on the call stack?

int h(int n) {
  if (n <= 1) return 0;
  return 1 + h(n / 2);
}
Show answer

Calls: h(20) → h(10) → h(5) → h(2) → h(1). h(1) is the base case and returns 0.

On the way back each frame adds 1: h(2) = 1, h(5) = 2, h(10) = 3, h(20) = 4 (⌊log₂ 20⌋).

5 calls; at the deepest point 5 frames + main = 6. Since n halves with every call, time and memory are O(log n).

Short Answer · Linear Recursion

Question 6: Missing Line: The Largest Item

The method should return the largest of the first n items of the array. What goes in the blank? For a = {4, 9, 2, 7}, how many calls does largest(a, 4) make?

int largest(int[] a, int n) {
  if (n == 1) return a[0];
  int m = largest(a, n - 1);
  return ______;
}
Show answer

The blank: Math.max(m, a[n - 1])

m is the largest of the first n − 1 items, so all that is left is to compare it with the last item, a[n − 1].

Calls with n = 4, 3, 2, 1: 4 calls. On the way back: 4 → max(4, 9) = 9 → max(9, 2) = 9 → max(9, 7) = 9. T(n) = T(n − 1) + c → O(n).

Multiple Choice · Linear Recursion

Question 7: Fast Exponentiation

How many times in total is fastPower(2, 13) called (counting the first call)?

long fastPower(long x, int n) {
  if (n == 0) return 1;
  long y = fastPower(x, n / 2);
  if (n % 2 == 0) return y * y;
  return x * y * y;
}

B: n takes the values 13, 6, 3, 1, 0 → 5 calls (⌊log₂ 13⌋ + 2). The result is 2¹³ = 8192. The slow power(2, 13) makes 14 calls.

Code Trace · Linear Recursion

Question 8: Tracing a Palindrome Check

What does the program print, and how many calls are made?

main calls isPalindrome("kiosk", 0, 4).

i = 0 < j = 4. s[0] = k, s[4] = k: equal → line 5 calls for the inner part.

s[1] = i, s[3] = s: different → base case, return false.

isPalindrome(0, 4) passes the false straight on (a tail call).

Output false; only 2 calls were made. The recursion stops at the first mismatch; in the worst case ⌊n/2⌋ + 1 calls, O(n).

boolean isPalindrome(String s, int i, int j) {
  if (i >= j) return true;
  if (s.charAt(i) != s.charAt(j))
    return false;
  return isPalindrome(s, i + 1, j - 1);
}
// main:
boolean p = isPalindrome("kiosk", 0, 4);
System.out.println(p);

Multiple Choice · Binary Recursion

Question 9: Calls Made by fib(5)

For the recursive fib(n) (returns n if n <= 1, otherwise fib(n − 1) + fib(n − 2)), how many calls does fib(5) make in total?

C: The number of calls is C(n) = C(n − 1) + C(n − 2) + 1, with C(0) = C(1) = 1: C(2) = 3, C(3) = 5, C(4) = 9, C(5) = 15 (= 2 · F(6) − 1). 9 is the count for fib(4), and 25 for fib(6).

Short Answer · Binary Recursion

Question 10: Repeated Subproblems

In the call tree of fib(5), how many times are fib(3), fib(2), fib(1) and fib(0) called? What is the largest number of fib frames on the stack at once?

Show answer
fib(5)fib(4)fib(3)fib(2)fib(1)fib(0)total
11235315

fib(3) is computed twice and fib(2) three times: the same subproblem is solved again and again.

The deepest chain is fib(5) → fib(4) → fib(3) → fib(2) → fib(1): 5 frames. Exponential time, O(n) memory.

Storing the values in an array computes each fib(k) once → O(n).

Multiple Choice · Binary Recursion

Question 11: An Item That Isn't There

For a = {3, 8, 15, 21, 30, 42, 57}, how many calls does the recursive binarySearch(a, 20, 0, 6) make, and what does it return?

B: (0, 6) mid = 3, 20 < 21 → (0, 2) mid = 1, 20 > 8 → (2, 2) mid = 2, 20 > 15 → (3, 2) lo > hi: empty range, −1. The last call counts too: 4 calls. 3 is where 20 could be inserted, but the method returns −1.

Short Answer · Hanoi and Backtracking

Question 12: Hanoi Moves

How many lines does hanoi(4, 'A', 'B', 'C') print in total, and what is the 8th line? Justify your answer from the recursive structure.

void hanoi(int n, char from, char via, char to) {
  if (n == 1) {
    System.out.println("1: " + from + "→" + to);
    return;
  }
  hanoi(n - 1, from, to, via);
  System.out.println(n + ": " + from + "→" + to);
  hanoi(n - 1, via, from, to);
}
Show answer

T(n) = 2T(n−1) + 1, T(1) = 1 → T(n) = 2ⁿ − 1. T(4) = 15 lines.

The call first moves the top 3 disks to B with hanoi(3, A, C, B): T(3) = 7 lines. Then the largest disk moves, so the 8th line is 4: A→C. The last 7 lines come from hanoi(3, B, A, C).

(Because n is even, the first move is 1: A→B.) Time O(2ⁿ); the call stack is at most n deep (+ main) → memory O(n).

Multiple Choice · Hanoi and Backtracking

Question 13: Hanoi: Line 5

What is line 5 of the output of hanoi(3, 'A', 'B', 'C')? (If n == 1 it prints "1: from→to"; otherwise hanoi(n − 1, from, to, via), "n: from→to", hanoi(n − 1, via, from, to).)

C: The first 3 lines come from hanoi(2, A, C, B) (1: A→C, 2: A→B, 1: C→B); line 4 is the largest disk: 3: A→C. The remaining lines come from hanoi(2, B, A, C), and the first of them is hanoi(1, B, C, A): 1: B→A.

Short Answer · Hanoi and Backtracking

Question 14: Order of the Branches

When generating subsets, each item has two choices: take it (a[i] is added to the subset) or leave it (a[i] is not added). The slides' subsets tries “take” first; the sub method below tries “leave” first. What does sub(new int[]{1, 2, 3}, 0, new ArrayList<>()) print? How does changing the order change the output?

void sub(int[] a, int i, List<Integer> s) {
  if (i == a.length) {
    System.out.print(s + " "); return;
  }
  sub(a, i + 1, s);          // 1) leave: without a[i]
  s.add(a[i]);
  sub(a, i + 1, s);          // 2) take: with a[i]
  s.remove(s.size() - 1);
}
Show answer

Output: [] [3] [2] [2, 3] [1] [1, 3] [1, 2] [1, 2, 3]

At every node the “leave” branch comes first. The slides' subsets takes the “take” branch first and prints [1, 2, 3] [1, 2] [1, 3] [1] [2, 3] [2] [3] [], so the output is exactly reversed. The subsets themselves are the same: still 8 subsets and 15 calls.

The undo (s.remove) comes after the second call, so s is back to what it was when the method started.

Multiple Choice · Hanoi and Backtracking

Question 15: Forgetting to Undo

If the line s.remove(s.size() - 1); is deleted from the subsets method, what is the output for {1, 2}?

void subsets(int[] a, int i,
             List<Integer> s) {
  if (i == a.length) {
    System.out.print(s + " "); return;
  }
  s.add(a[i]);
  subsets(a, i + 1, s);
  // the s.remove(...) line was deleted
  subsets(a, i + 1, s);
}

B: No choice is ever undone. The left branch prints [1, 2], and the right branch sees the same list: [1, 2]. When the root moves to its right branch, s is still [1, 2]; 2 is added again, and [1, 2, 2] is printed twice. The correct output would be A.

Short Answer · Hanoi and Backtracking

Question 16: Counting Permutations

At each level, the perm method below picks one of the letters not yet used, from left to right. For perm("ABCD".toCharArray(), new boolean[4], new StringBuilder()), how many permutations are printed, how many calls are made, and what are the first three outputs?

void perm(char[] c, boolean[] used,
          StringBuilder s) {
  if (s.length() == c.length) { // base
    System.out.print(s + " "); return;
  }
  for (int k = 0; k < c.length; k++) {
    if (used[k]) continue; // used
    used[k] = true; s.append(c[k]); // choose
    perm(c, used, s);
    used[k] = false;       // undo
    s.deleteCharAt(s.length() - 1);
  }
}
Show answer

4! = 24 permutations.

Levels of the call tree: 1 (empty) + 4 + 4 · 3 + 4 · 3 · 2 + 4 · 3 · 2 · 1 = 1 + 4 + 12 + 24 + 24 = 65 calls.

First outputs: ABCD ABDC ACBD. Stack depth n + 1 = 5 frames; time O(n · n!).

Multiple Choice · Recurrence Relations

Question 17: Recognizing the Recurrence

Which is the recurrence for this method, and its solution?

void print(int n) {
  if (n == 0) return;
  print(n - 1);
  print(n - 1);
  System.out.print(n);
}

B: Each call makes two calls of size n − 1; level k has 2ᵏ calls, 2ⁿ⁺¹ − 1 calls in total. print(3) makes 15 calls and prints 1121123.

Short Answer · Recurrence Relations

Question 18: Solving with a Call Tree

What does count(8) return, and how many calls does it make in total? What is the largest number of count frames on the stack at once? Draw the call tree level by level.

int count(int n) {
  if (n <= 1) return 1;
  return count(n / 2) + count(n / 2);
}
Show answer
Level0123
n8421
calls1248

In total 1 + 2 + 4 + 8 = 15 calls (2n − 1). Each of the 8 leaves returns 1: count(8) = 8.

The deepest chain is 8 → 4 → 2 → 1: 4 frames (log₂ n + 1). T(n) = 2T(n/2) + c → O(n) time, O(log n) memory.

Multiple Choice · Tail Recursion

Question 19: Which One Is Tail Recursive?

Which of these recursive steps is tail recursive?

C: The call is the method's last action; the addition happens before the call, and the returned value is passed on unchanged. In A and B a multiplication or addition happens after the call returns, and in D a print does.

Short Answer · Tail Recursion

Question 20: Converting to a Loop

Through which chain of calls does sumTail(4, 0) return, and what value? Write the loop equivalent of the method. Which one uses less memory in Java?

int sumTail(int n, int acc) {
  if (n == 0) return acc;
  return sumTail(n - 1, acc + n);
}
Show answer

(4, 0) → (3, 4) → (2, 7) → (1, 9) → (0, 10): the base case returns acc = 10, which is passed back unchanged; 5 calls.

int sumLoop(int n) {
  int acc = 0;
  while (n != 0) {
    acc = acc + n;
    n = n - 1;
  }
  return acc;
}

Java does not optimize tail calls: the recursive version uses O(n) stack, the loop O(1).

Multiple Choice · Recursion on Linked Lists

Question 21: Recursion over a List

If the list is 5 → 8 → 2 → 3 → null, what does f(head) print?

void f(Node node) {
  if (node == null) return;
  if (node.data % 2 == 0)
    System.out.print(node.data + " ");
  f(node.next);
  if (node.data % 2 == 1)
    System.out.print(node.data + " ");
}

A: Even values are printed before the call, in list order: 8 2. Odd values are printed after the call, that is, from last to first as the stack unwinds: 3 5.

Open-Ended Question · Recursion on Linked Lists

Question 22: Summing a Long List

A sensor appends its readings to a singly linked list that can hold a million nodes. To total the readings, should you use a recursive method of the form return node.data + sum(node.next); or a loop? Justify your answer.

Sample answer

The recursive version is short and close to the definition, but it pushes one frame per node: a depth of n = 10⁶. The default Java stack cannot hold that many frames → StackOverflowError.

Even written tail recursively (with an accumulator), the problem remains, because Java does not optimize tail calls.

A loop (for (Node node = head; node != null; node = node.next) total += node.data;) runs in O(n) time and O(1) memory. Since the total may overflow, use a long.

The End

Chapter 4: Recursion: Study Questions
Lecture slides: Chapter 4: Recursion
1 / 1 Sercan KÜLCÜ. All rights reserved.