Recursion is a powerful technique for solving problems by breaking them into smaller subproblems. However, one common question that arises is: When do we need a for-loop inside the recursive function, and when do we not?
In this blog, weβll explore the patterns that help us determine whether a loop is required inside a recursive function or not.
π Key Differences: Problems With and Without Loops
| Problem Type | Requires For-Loop? | Example Problems |
|---|---|---|
| Backtracking | β Yes | N-Queens, Sudoku Solver, Word Search, Combination Sum, All Permutations |
| Combinatorial Enumeration | β Yes | Generating all subsets, Palindrome Partitioning, Generating all combinations |
| Tree/Graph Traversal | β Yes | DFS (Depth-First Search), BFS (Breadth-First Search) |
| Divide and Conquer | β No | Merge Sort, Quick Sort, Binary Search, Power Calculation |
| Subset Generation | β No | Generating all subsets (Power Set), Subset Sum, Unique Subsets |
| Mathematical Recurrence Relations | β No | Fibonacci Sequence, Factorial, Tower of Hanoi |
π§ Pattern 1: Use a Loop When Exploring Multiple Choices Per Step
πΉ Example: Combination Sum (Uses a Loop)
Why? At each step, we have multiple choices (each candidate can be picked multiple times). So, we iterate over candidates and call the function recursively.
public static void combinationSum(int[] candidates, int target, int start, List<Integer> temp, List<List<Integer>> result) {
if (target == 0) {
result.add(new ArrayList<>(temp));
return;
}
for (int i = start; i < candidates.length; i++) { // Iterating over choices
if (target >= candidates[i]) {
temp.add(candidates[i]);
combinationSum(candidates, target - candidates[i], i, temp, result); // Recursive call inside loop
temp.remove(temp.size() - 1);
}
}
}
πΉ Example: Generating All Permutations (Uses a Loop)
Why? At each step, we pick a number and explore all possible placements.
public static void allPermutations(int[] nums, List<Integer> temp, boolean[] used, List<List<Integer>> result) {
if (temp.size() == nums.length) {
result.add(new ArrayList<>(temp));
return;
}
for (int i = 0; i < nums.length; i++) { // Looping through choices
if (!used[i]) {
used[i] = true;
temp.add(nums[i]);
allPermutations(nums, temp, used, result); // Recursive call inside loop
temp.remove(temp.size() - 1);
used[i] = false;
}
}
}
π Pattern: When at each recursive step you need to choose from multiple possibilities, you typically use a loop.
π§ Pattern 2: No Loop When Making Binary (Yes/No) Decisions
πΉ Example: Generating All Subsets (No Loop)
Why? At each step, we have only two choices: either include or exclude the current element.
public static void allSubsets(int[] nums, int index, List<Integer> temp, List<List<Integer>> result) {
if (index == nums.length) {
result.add(new ArrayList<>(temp));
return;
}
temp.add(nums[index]);
allSubsets(nums, index + 1, temp, result); // Include the element
temp.remove(temp.size() - 1);
allSubsets(nums, index + 1, temp, result); // Exclude the element
}
πΉ Example: Subset Sum (No Loop)
Why? At each step, we either add the current element to the sum or ignore it.
public static void subsetSum(int[] nums, int index, int sum, List<Integer> result) {
if (index == nums.length) {
result.add(sum);
return;
}
subsetSum(nums, index + 1, sum + nums[index], result); // Include the element
subsetSum(nums, index + 1, sum, result); // Exclude the element
}
π Pattern: When at each recursive step you are deciding between two choices (include/exclude), you typically do not need a loop.
π§ Pattern 3: No Loop in Divide and Conquer Problems
πΉ Example: Merge Sort (No Loop in Recursive Calls)
Why? The problem is divided into two equal halves, and the recursive function is called on each half separately.
public static void mergeSort(int[] arr, int left, int right) {
if (left < right) {
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
π Pattern: When the problem follows a divide-and-conquer approach (like Merge Sort, Quick Sort, or Binary Search), you do not use a loop in the recursive function.
π― Conclusion: When to Use a Loop?
Use a loop inside recursion when:
β
You need to explore multiple choices at each step (Backtracking, Combinatorial problems, Graph Traversal).
β
You need to iterate through a set of possibilities (Generating permutations, Combination Sum, Sudoku Solver).
Avoid a loop inside recursion when:
β You are making binary (yes/no) decisions (Subset Sum, Generating All Subsets).
β The problem follows a divide-and-conquer approach (Merge Sort, Quick Sort, Fibonacci).
Understanding these patterns can help you determine when to use a loop in recursive functions. Apply these insights to your problem-solving approach and level up your recursion skills! π
Top comments (0)