0% found this document useful (0 votes)
2 views10 pages

Chapter 09 Recursion in Java

Chapter 9 covers recursion in Java, explaining its fundamental concepts, including base and recursive cases, and various patterns such as classic problems (factorial, Fibonacci), array recursion, string recursion, and divide & conquer techniques like merge sort and quick sort. It also discusses the Tower of Hanoi, flood fill algorithms, and generating subsets and permutations. The chapter concludes with a comparison of recursion and iteration, highlighting their respective advantages and disadvantages.

Uploaded by

quantalgo.labs
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views10 pages

Chapter 09 Recursion in Java

Chapter 9 covers recursion in Java, explaining its fundamental concepts, including base and recursive cases, and various patterns such as classic problems (factorial, Fibonacci), array recursion, string recursion, and divide & conquer techniques like merge sort and quick sort. It also discusses the Tower of Hanoi, flood fill algorithms, and generating subsets and permutations. The chapter concludes with a comparison of recursion and iteration, highlighting their respective advantages and disadvantages.

Uploaded by

quantalgo.labs
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

Chapter 9

Recursion in Java
Java Data Structures & Algorithms Series

Recursion is a function that calls itself to solve a smaller version of the same problem. Every
recursive solution has two parts: a base case (stop condition) and a recursive case (smaller
subproblem). It is the foundation for Backtracking, Divide & Conquer, Trees, and DP.

1 The Recursion Mental Model

solve(problem):
if problem is trivial → return answer directly ← BASE CASE
else → solve(smaller problem) + combine result ← RECURSIVE CASE

Key: TRUST that solve(smaller) works correctly.


Your job is only to define the base case and the combination step.

⭐ Recursion Hypothesis: Assume the recursive call for a smaller input works perfectly.
You only need to define:
1. Base Case — when do we stop?
2. Combination — how do we use the smaller answer to build the full answer?

How the Call Stack Works

factorial(4)
└─ 4 × factorial(3)
└─ 3 × factorial(2)
└─ 2 × factorial(1)
└─ return 1 ← base case
└─ return 2×1 = 2
└─ return 3×2 = 6
└─ return 4×6 = 24

2 Pattern 1 — Classic Recursion Problems

Factorial

public static int factorial(int n) {


if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case
}
// Time: O(n) | Space: O(n) stack frames
Fibonacci

public static int fib(int n) {


if (n <= 1) return n; // fib(0)=0, fib(1)=1
return fib(n-1) + fib(n-2);
}
// Time: O(2^n) — exponential without memoization!
// fib(5): fib(4)+fib(3) → fib(3)+fib(2)+fib(2)+fib(1) → many repeated calls

⚠️ Naive Fibonacci is O(2^n). Always add memoization (Day 13 — DP) for large n.
With memoization it drops to O(n) time and O(n) space.

Sum of Digits

public static int sumOfDigits(int n) {


if (n == 0) return 0;
return (n % 10) + sumOfDigits(n / 10);
}
// sumOfDigits(1234) = 4 + sumOfDigits(123)
// = 4 + 3 + sumOfDigits(12)
// = 4 + 3 + 2 + sumOfDigits(1)
// = 4 + 3 + 2 + 1 = 10 ✅

Fast Exponentiation — Power(x, n)

public static double power(double base, int exp) {


if (exp == 0) return 1;
if (exp < 0) return 1.0 / power(base, -exp);

double half = power(base, exp / 2); // compute half once


if (exp % 2 == 0) return half * half;
else return base * half * half;
}
// Time: O(log n) | Space: O(log n)
// Half the exponent each call — far better than O(n) repeated multiplication

3 Pattern 2 — Array Recursion

Reverse an Array

public static void reverseArray(int[] arr, int left, int right) {


if (left >= right) return; // base case
int temp = arr[left];
arr[left] = arr[right];
arr[right] = temp;
reverseArray(arr, left + 1, right - 1); // recurse on inner elements
}

Check if Array is Sorted

public static boolean isSorted(int[] arr, int i) {


if (i == [Link] - 1) return true; // single element → sorted
if (arr[i] > arr[i+1]) return false; // violation found
return isSorted(arr, i + 1);
}

Linear Search — Return All Matching Indices

public static List<Integer> linearSearch(int[] arr, int target,


int i, List<Integer> result) {
if (i == [Link]) return result;
if (arr[i] == target) [Link](i);
return linearSearch(arr, target, i + 1, result);
}

4 Pattern 3 — String Recursion

Reverse a String

public static String reverse(String s) {


if ([Link]() <= 1) return s;
return reverse([Link](1)) + [Link](0);
}
// reverse("hello") = reverse("ello") + 'h'
// = reverse("llo") + 'e' + 'h'
// = ... = "olleh" ✅

Check Palindrome

public static boolean isPalindrome(String s, int left, int right) {


if (left >= right) return true; // base case
if ([Link](left) != [Link](right)) return false; // mismatch
return isPalindrome(s, left + 1, right - 1);
}

All Subsequences of a String

// "abc" → "", "a", "b", "c", "ab", "ac", "bc", "abc" (2^n total)
public static void subsequences(String s, int i, String current) {
if (i == [Link]()) {
[Link]([Link]() ? """" : current);
return;
}
subsequences(s, i + 1, current + [Link](i)); // include s[i]
subsequences(s, i + 1, current); // exclude s[i]
}
// Time: O(2^n) | Space: O(n) stack depth

5 Pattern 4 — Divide & Conquer

Merge Sort

public static void mergeSort(int[] arr, int left, int right) {


if (left >= right) return; // base case: single element

int mid = left + (right - left) / 2;


mergeSort(arr, left, mid); // sort left half
mergeSort(arr, mid + 1, right); // sort right half
merge(arr, left, mid, right); // combine
}

private static void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;

while (i <= mid && j <= right)


temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];

[Link](temp, 0, arr, left, [Link]);


}
// Time: O(n log n) | Space: O(n)

Quick Sort

public static void quickSort(int[] arr, int low, int high) {


if (low >= high) return;
int pivot = partition(arr, low, high);
quickSort(arr, low, pivot - 1);
quickSort(arr, pivot + 1, high);
}

private static int partition(int[] arr, int low, int high) {


int pivot = arr[high], i = low - 1;
for (int j = low; j < high; j++) {
if (arr[j] <= pivot) {
i++;
int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
}
}
int temp = arr[i+1]; arr[i+1] = arr[high]; arr[high] = temp;
return i + 1;
}
// Time: O(n log n) avg, O(n²) worst | Space: O(log n)

6 Pattern 5 — Tower of Hanoi

Problem: Move N disks from source to destination using one auxiliary peg. Never place a larger disk
on a smaller one.

N=3 requires 2^3 - 1 = 7 moves minimum.


Observation: to move disk N, you must first move disks 1..N-1 to auxiliary.

public static void hanoi(int n, char src, char aux, char dest) {
if (n == 1) {
[Link]("Move disk 1: " + src + " → " + dest);
return;
}
hanoi(n - 1, src, dest, aux); // move top n-1 disks to aux
[Link]("Move disk " + n + ": " + src + " → " + dest);
hanoi(n - 1, aux, src, dest); // move n-1 disks from aux to dest
}
// Time: O(2^n) | Space: O(n) | Minimum moves = 2^n - 1

// hanoi(3, 'A', 'B', 'C') output:


// Move disk 1: A → C
// Move disk 2: A → B
// Move disk 1: C → B
// Move disk 3: A → C
// Move disk 1: B → A
// Move disk 2: B → C
// Move disk 1: A → C

7 Pattern 6 — Flood Fill (Recursion on Grid)

Problem: Given a 2D image, paint all connected pixels of the same colour starting from (sr, sc).

image: After floodFill(1,1, newColor=2):


1 1 1 2 2 2
1 1 0 → 2 2 0
1 0 1 2 0 1

public static int[][] floodFill(int[][] image, int sr, int sc, int newColor) {
int oldColor = image[sr][sc];
if (oldColor != newColor)
fill(image, sr, sc, oldColor, newColor);
return image;
}

private static void fill(int[][] img, int r, int c, int old, int newC) {
if (r < 0 || r >= [Link]) return; // out of bounds
if (c < 0 || c >= img[0].length) return; // out of bounds
if (img[r][c] != old) return; // different colour

img[r][c] = newC;
fill(img, r+1, c, old, newC); // down
fill(img, r-1, c, old, newC); // up
fill(img, r, c+1, old, newC); // right
fill(img, r, c-1, old, newC); // left
}
// Time: O(m×n) | Space: O(m×n) recursion depth

8 Pattern 7 — Generate All Subsets (Power Set)

public static List<List<Integer>> subsets(int[] nums) {


List<List<Integer>> result = new ArrayList<>();
generateSubsets(nums, 0, new ArrayList<>(), result);
return result;
}

// Approach A — loop-based (adds snapshot at every recursive node)


private static void generateSubsets(int[] nums, int i,
List<Integer> current,
List<List<Integer>> result) {
[Link](new ArrayList<>(current)); // snapshot here = valid subset
for (int j = i; j < [Link]; j++) {
[Link](nums[j]);
generateSubsets(nums, j + 1, current, result);
[Link]([Link]() - 1); // backtrack
}
}

// Approach B — include/exclude pattern


private static void subsets2(int[] nums, int i,
List<Integer> cur, List<List<Integer>> res) {
if (i == [Link]) { [Link](new ArrayList<>(cur)); return; }
[Link](nums[i]);
subsets2(nums, i+1, cur, res); // include nums[i]
[Link]([Link]()-1);
subsets2(nums, i+1, cur, res); // exclude nums[i]
}
// Time: O(2^n × n) | Space: O(n)

9 Pattern 8 — Generate All Permutations

public static List<List<Integer>> permute(int[] nums) {


List<List<Integer>> result = new ArrayList<>();
permuteHelper(nums, new ArrayList<>(), new boolean[[Link]], result);
return result;
}

// Approach A — used[] array


private static void permuteHelper(int[] nums, List<Integer> current,
boolean[] used, List<List<Integer>> result) {
if ([Link]() == [Link]) {
[Link](new ArrayList<>(current));
return;
}
for (int i = 0; i < [Link]; i++) {
if (used[i]) continue;
used[i] = true;
[Link](nums[i]);
permuteHelper(nums, current, used, result);
[Link]([Link]() - 1);
used[i] = false;
}
}

// Approach B — swap-based (in-place, more efficient)


public static void permuteSwap(int[] nums, int start, List<List<Integer>> result) {
if (start == [Link]) {
List<Integer> perm = new ArrayList<>();
for (int n : nums) [Link](n);
[Link](perm);
return;
}
for (int i = start; i < [Link]; i++) {
swap(nums, start, i);
permuteSwap(nums, start + 1, result);
swap(nums, start, i); // backtrack: restore
}
}
// Time: O(n! × n) | Space: O(n)
10 Pattern 9 — Recursion with Return Values

Count All Subsequences with Sum = Target

public static int countSubsequences(int[] arr, int i, int target) {


if (target == 0) return 1; // found a valid subsequence
if (i == [Link] || target < 0) return 0; // exhausted / overshot

int include = countSubsequences(arr, i+1, target - arr[i]);


int exclude = countSubsequences(arr, i+1, target);
return include + exclude;
}

Find Any One Subsequence with Sum = Target

public static boolean findSubsequence(int[] arr, int i, int target,


List<Integer> result) {
if (target == 0) return true; // found — stop searching
if (i == [Link] || target < 0) return false;

[Link](arr[i]);
if (findSubsequence(arr, i+1, target - arr[i], result)) return true;
[Link]([Link]() - 1); // backtrack if not found
return findSubsequence(arr, i+1, target, result);
}
// Key: return true short-circuits — stops as soon as any answer is found

11 Recursion Tree Visualization

subsets([1,2,3]) — include/exclude tree:

[]
/ \
[1] []
/ \ / \
[1,2] [1] [2] []
/ \ / \ / \ / \
[1,2,3][1,2][1,3][1] [2,3][2][3] []

Total nodes = 2^n = 8 — each leaf is a valid subset ✅

permute([1,2]) — swap tree:


[1,2] [2,1]
start=0: swap(0,0) swap(0,1)
→ recurse start=1 each

12 Recursion vs Iteration

Factor Recursion Iteration


Readability ✅ Cleaner for tree / graph Verbose for deeply nested logic
problems
Stack space ❌ O(n) call stack frames ✅ O(1)
Stack overflow ❌ Risk for very deep n ✅ No risk
Tail recursion JVM does NOT optimize tail calls N/A
Best for Trees, graphs, divide & conquer Simple loops, array scans

13 Full Runnable Java Program

import [Link].*;

public class Chapter9Recursion {

public static void main(String[] args) {


[Link]("Factorial(5): " + factorial(5)); // 120
[Link]("Fib(7): " + fib(7)); // 13
[Link]("SumDigits(1234): " + sumOfDigits(1234)); // 10
[Link]("Power(2,10): " + power(2, 10)); // 1024.0

int[] arr = {1,2,3,4,5};


reverseArray(arr, 0, [Link]-1);
[Link]("Reversed: " + [Link](arr)); //
[5,4,3,2,1]
[Link]("Sorted check: " + isSorted(new int[]{1,2,3,4,5}, 0));
// true

[Link]("Reverse 'hello': " + reverse("hello")); // olleh


[Link]("Palindrome 'racecar': " +
isPalindrome("racecar", 0, 6)); // true

[Link]("Subsequences 'ab': ");


subsequences("ab", 0, ""); [Link]();

int[] arr2 = {5,2,8,1,9,3};


mergeSort(arr2, 0, [Link]-1);
[Link]("Merge Sorted: " + [Link](arr2)); //
[1,2,3,5,8,9]

int[] arr3 = {5,2,8,1,9,3};


quickSort(arr3, 0, [Link]-1);
[Link]("Quick Sorted: " + [Link](arr3)); //
[1,2,3,5,8,9]

[Link]("Hanoi(3):");
hanoi(3, 'A', 'B', 'C');

[Link]("Subsets [1,2,3]: " + subsets(new int[]{1,2,3}));


[Link]("Permutations [1,2,3]: " + permute(new int[]{1,2,3}));

[Link]("Count subseq sum=3: " +


countSubsequences(new int[]{1,2,1}, 0, 3)); // 2
}

static int factorial(int n) { return n<=1?1:n*factorial(n-1); }


static int fib(int n) { return n<=1?n:fib(n-1)+fib(n-2); }
static int sumOfDigits(int n){ return n==0?0:(n%10)+sumOfDigits(n/10); }
static double power(double b,int e){ if(e==0)return 1;if(e<0)return
1.0/power(b,-e);double h=power(b,e/2);return e%2==0?h*h:b*h*h; }

static void reverseArray(int[] a,int l,int r){ if(l>=r)return;int


t=a[l];a[l]=a[r];a[r]=t;reverseArray(a,l+1,r-1); }
static boolean isSorted(int[] a,int i){ if(i==[Link]-1)return
true;if(a[i]>a[i+1])return false;return isSorted(a,i+1); }

static String reverse(String s){ return [Link]()<=1?s:reverse([Link](1))


+[Link](0); }
static boolean isPalindrome(String s,int l,int r){ if(l>=r)return
true;if([Link](l)!=[Link](r))return false;return isPalindrome(s,l+1,r-1); }
static void subsequences(String s,int i,String cur){
if(i==[Link]()){[Link]("["+cur+"] ");return;}
subsequences(s,i+1,cur+[Link](i)); subsequences(s,i+1,cur);
}

static void mergeSort(int[] a,int l,int r){


if(l>=r)return; int mid=l+(r-l)/2; mergeSort(a,l,mid); mergeSort(a,mid+1,r);
int[] t=new int[r-l+1]; int i=l,j=mid+1,k=0;
while(i<=mid&&j<=r) t[k++]=a[i]<=a[j]?a[i++]:a[j++];
while(i<=mid) t[k++]=a[i++]; while(j<=r) t[k++]=a[j++];
[Link](t,0,a,l,[Link]);
}
static void quickSort(int[] a,int lo,int hi){
if(lo>=hi)return; int p=partition(a,lo,hi); quickSort(a,lo,p-1);
quickSort(a,p+1,hi);
}
static int partition(int[] a,int lo,int hi){
int piv=a[hi],i=lo-1;
for(int j=lo;j<hi;j++) if(a[j]<=piv){i++;int t=a[i];a[i]=a[j];a[j]=t;}
int t=a[i+1];a[i+1]=a[hi];a[hi]=t; return i+1;
}

static void hanoi(int n,char s,char a,char d){


if(n==1){[Link]("Move disk 1: "+s+"→"+d);return;}
hanoi(n-1,s,d,a); [Link]("Move disk "+n+": "+s+"→"+d); hanoi(n-
1,a,s,d);
}

static List<List<Integer>> subsets(int[] nums){


List<List<Integer>> res=new ArrayList<>(); generateSubsets(nums,0,new
ArrayList<>(),res); return res;
}
static void generateSubsets(int[] nums,int i,List<Integer>
cur,List<List<Integer>> res){
[Link](new ArrayList<>(cur));
for(int j=i;j<[Link];j++)
{[Link](nums[j]);generateSubsets(nums,j+1,cur,res);[Link]([Link]()-1);}
}

static List<List<Integer>> permute(int[] nums){


List<List<Integer>> res=new ArrayList<>(); permuteSwap(nums,0,res); return
res;
}
static void permuteSwap(int[] nums,int start,List<List<Integer>> res){
if(start==[Link]){List<Integer> p=new ArrayList<>();for(int
n:nums)[Link](n);[Link](p);return;}
for(int i=start;i<[Link];i++)
{swap(nums,start,i);permuteSwap(nums,start+1,res);swap(nums,start,i);}
}
static void swap(int[] a,int i,int j){ int t=a[i];a[i]=a[j];a[j]=t; }

static int countSubsequences(int[] a,int i,int t){


if(t==0)return 1; if(i==[Link]||t<0)return 0;
return countSubsequences(a,i+1,t-a[i])+countSubsequences(a,i+1,t);
}
}

14 Practice Problems for Chapter 9


Solve in this order:

Difficulty Problem
Easy Fibonacci Number (LeetCode #509)
Easy Power of Two using Recursion (LeetCode #231)
Easy Reverse String (LeetCode #344)
Easy Flood Fill (LeetCode #733)
Medium Subsets (LeetCode #78)
Medium Permutations (LeetCode #46)
Medium Sort an Array — Merge Sort (LeetCode #912)
Medium Pow(x, n) — Fast Exponentiation (LeetCode #50)
Medium Generate All Subsequences with Sum K (GFG)
Hard Tower of Hanoi (GFG)

💡 Key Insight: The most common recursion mistake is not trusting the recursive call.

Don't trace through the entire recursion tree in your head.


Instead, apply the Recursion Hypothesis:
1. Define the base case — when is the answer trivially known?
2. Assume recursion(smaller) returns the correct answer.
3. Use that answer to build the full solution.

This leap of faith is the key mental shift that makes complex recursion feel simple.

Next up is Chapter 10 — Backtracking! 🚀

You might also like