Lesson 5
Advanced Recursion Techniques in C++
Lesson Overview

Welcome to this dive deep into Advanced Recursion Techniques. These techniques will not only broaden your understanding of the concept but also equip you with the ability to tackle complex problems comfortably. Recursion, simply put, is a method where the solution to a problem depends on smaller instances of the same problem. Advanced recursion techniques allow us to solve problems involving deep tree structures and backtracking, which are quite common in certain interview questions.

Quick Example

To give you a small taste of what's in store, let's look at a recursive function that generates all permutations of a vector of numbers. The strategy here is to use a method known as backtracking. Backtracking is a general algorithm for finding all (or some) solutions to some computational problems. In our example, we recursively swap all elements (for each index from the first to the last), moving one step further into the depth of the vector after each recursion until we reach the end. Once we get there, we append the current state of the vector to our results vector.

The permute algorithm generates all permutations of a vector of numbers using a technique called backtracking. Here's a detailed breakdown of how it works:

  1. Initialize Result Storage:
    • A vector result is initialized to store all the permutations.
  2. Recursive Backtracking Function:
    • A recursive function backtrack is defined, which takes an integer first representing the starting index for permutations and the current state of the vector nums.
  3. Base Case:
    • If first equals the size of the input vector nums, it means a full permutation has been formed, which is then added to the result.
  4. Permutations Through Swapping:
    • Loop through the vector starting from the first index to the end.
    • Swap the element at the first index with the current index i.
    • Recursively call backtrack with first + 1 to continue forming the next part of the permutation.
    • Swap back the elements to revert the vector to its previous state (backtrack) and explore other permutations.
  5. Execution:
    • The initial call to backtrack starts with first set to 0.
    • The generated permutations are stored in the result vector, which is then returned.

By using backtracking, this algorithm methodically explores all possible permutations of the given vector by swapping and recursively building permutations one element at a time.

The implementation of the algorithm is:

C++
1#include <iostream> 2#include <vector> 3#include <algorithm> // for std::swap() 4 5void backtrack(std::vector<int>& nums, int first, std::vector<std::vector<int>>& result) { 6 if (first == nums.size()) { 7 result.push_back(nums); 8 return; 9 } 10 for (int i = first; i < nums.size(); ++i) { 11 std::swap(nums[first], nums[i]); // Swap numbers 12 backtrack(nums, first + 1, result); 13 std::swap(nums[first], nums[i]); // Swap them back to reset the state 14 } 15} 16 17std::vector<std::vector<int>> permute(std::vector<int>& nums) { 18 std::vector<std::vector<int>> result; 19 backtrack(nums, 0, result); 20 return result; 21} 22 23int main() { 24 std::vector<int> nums = {1, 2, 3}; 25 std::vector<std::vector<int>> permutations = permute(nums); 26 for (const auto& permutation : permutations) { 27 for (int num : permutation) { 28 std::cout << num << " "; 29 } 30 std::cout << std::endl; 31 } 32 return 0; 33}
Example Walkthrough

Let's walkthrough the permute algorithm using the vector {1, 2, 3} step-by-step:

  1. Initial Setup:

    • Input vector: {1, 2, 3}
    • Initial call: backtrack(nums, 0, result)
  2. First Level of Recursion (backtrack(nums, 0, result)):

    • first = 0:
      • Swap nums[0] with nums[0] (no change): {1, 2, 3}
      • Call backtrack(nums, 1, result)
  3. Second Level of Recursion (backtrack(nums, 1, result)):

    • first = 1:
      • Swap nums[1] with nums[1] (no change): {1, 2, 3}
      • Call backtrack(nums, 2, result)
  4. Third Level of Recursion (backtrack(nums, 2, result)):

    • first = 2:
      • Swap nums[2] with nums[2] (no change): {1, 2, 3}
      • Call backtrack(nums, 3, result)
  5. Base Case Reached (backtrack(nums, 3, result)):

    • first = 3:
      • Add {1, 2, 3} to result
    • Return to backtrack(nums, 2, result)
  6. Backtrack Step in backtrack(nums, 2, result):

    • Revert swap nums[2] with nums[2]: {1, 2, 3}
    • No more elements to swap, return to backtrack(nums, 1, result)
  7. Backtrack Step in backtrack(nums, 1, result):

    • Revert swap nums[1] with nums[1]: {1, 2, 3}
    • Swap nums[1] with nums[2]: {1, 3, 2}
    • Call backtrack(nums, 2, result)
  8. Recursive Call (backtrack(nums, 2, result) with {1, 3, 2}):

    • first = 2:
      • Swap nums[2] with nums[2] (no change): {1, 3, 2}
      • Call backtrack(nums, 3, result)
  9. Base Case Reached (backtrack(nums, 3, result)):

    • first = 3:
      • Add {1, 3, 2} to result
    • Return to backtrack(nums, 2, result)
  10. Backtrack Step in backtrack(nums, 2, result):

    • Revert swap nums[2] with nums[2]: {1, 3, 2}
    • No more elements to swap, return to backtrack(nums, 1, result)
  11. Backtrack Step in backtrack(nums, 1, result):

    • Revert swap nums[2] with nums[1]: {1, 2, 3}
    • No more elements to swap, return to backtrack(nums, 0, result)
  12. Backtrack Step in backtrack(nums, 0, result):

    • Revert swap nums[0] with nums[0]: {1, 2, 3}
    • Swap nums[0] with nums[1]: {2, 1, 3}
    • Call backtrack(nums, 1, result)

Following similar steps as above, the rest of the permutations will be generated by backtracking further. After backtracking through all levels and swapping appropriately, the final result vector will include all permutations of {1, 2, 3}:

  • {1, 2, 3}
  • {1, 3, 2}
  • {2, 1, 3}
  • {2, 3, 1}
  • {3, 1, 2}
  • {3, 2, 1}
Coming up: More Practice!

Now that we've briefly touched on what advanced recursion techniques are about, it's time to roll up your sleeves and delve into some practice problems. Through these exercises, you will gain a solid understanding of how to apply these techniques to real-world problems and be ready to shine in your interviews. Remember, the key to mastering recursion is understanding and practice. Let's get started!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.