Recursion & Induction Refresher

রিকার্শন ও ইনডাকশন

Read: ~35 min Beginner 8 practice problems Live code runner

1. Recursion = Induction in Code

Mathematical induction has two parts: a base case (true for n = 0 or n = 1) and an inductive step (if true for n−1, then true for n). A recursive function has the same two parts: a base case that returns directly, and a recursive case that calls itself on a smaller input. The act of writing a correct recursion is the act of writing an induction proof.

গণিতের ইনডাকশন প্রমাণে দুটি অংশ থাকে — base case (n=0 বা n=1-এর জন্য সত্য) এবং inductive step (n−1-এর জন্য সত্য হলে n-এর জন্যও সত্য)। একটি recursive ফাংশনেও ঠিক এই দুটি অংশই থাকে। তাই একটি সঠিক recursive ফাংশন লেখা মানে আসলে একটি ইনডাকশন প্রমাণ লেখা।
Recipe for any recursion (1) What is the smallest case I can answer instantly? — that is the base case.
(2) Assume I can solve the problem for n − 1. How do I extend that to n? — that is the recursive step.
যেকোনো রিকার্শনের জন্য — base case + recursive step লিখতে পারলেই কাজ শেষ।

2. Factorial — The Hello World of Recursion

n! = n × (n−1)! with 0! = 1. Translates to four lines of C++:

factorial.cpp
#include <bits/stdc++.h>
using namespace std;

long long fact(int n) {
    if (n <= 1) return 1;       // base case
    return n * fact(n - 1);      // recursive step
}

int main() {
    for (int i = 0; i <= 10; i++)
        cout << i << "! = " << fact(i) << "\n";
}
৪ লাইনেই পুরো factorial — কারণ আমরা গণিতের সংজ্ঞাটাই কোডে অনুবাদ করেছি। n! = n × (n−1)! এবং 0! = 1 — এই দুটি লাইনই যথেষ্ট।

3. The Call Stack — Where Recursion Lives

Each recursive call gets its own stack frame on the program's call stack: return address, parameters, local variables. When the call returns, the frame is popped. Too deep a recursion → stack overflow.

fib(5) fib(4) fib(3) fib(3) fib(2) fib(2) fib(1) fib(2) fib(1) Figure 4.1 — Recursion tree of fib(5): notice how fib(2) and fib(3) are computed many times. This is the seed of dynamic programming.

4. Fibonacci, Hanoi & String Reverse

Three classic recursive patterns. Run them — pay attention to the call counts.

classics.cpp
#include <bits/stdc++.h>
using namespace std;

long long fib(int n) {
    if (n < 2) return n;
    return fib(n-1) + fib(n-2);
}

void hanoi(int n, char from, char to, char via) {
    if (n == 0) return;
    hanoi(n-1, from, via, to);
    cout << "Move disk " << n << " from " << from << " to " << to << "\n";
    hanoi(n-1, via, to, from);
}

void rev(string& s, int i, int j) {
    if (i >= j) return;
    swap(s[i], s[j]);
    rev(s, i+1, j-1);
}

int main() {
    cout << "fib(10) = " << fib(10) << "\n";
    hanoi(3, 'A', 'C', 'B');
    string s = "hello";
    rev(s, 0, s.size()-1);
    cout << "reversed: " << s << "\n";
}
Tower of Hanoi-এর পুরো সমাধান মাত্র ৩ লাইনের recursion-এ। এ থেকেই বোঝা যায় — কঠিন সমস্যাকে নিজের ছোট রূপে ভাঙতে পারলে কোড অসাধারণ ছোট হয়ে যায়।

5. Tail Recursion & the Bridge to DP

A recursive call is tail-recursive if it is the very last operation in the function — no work happens after it returns. Some compilers turn tail recursion into a loop (no extra stack frames). Naive Fibonacci is not tail-recursive, and it recomputes the same values many times — which is exactly why we need memoisation and dynamic programming (Modules 34–36).

✅ Recursion is great when

  • The problem has a clean recursive definition (trees, divide-and-conquer)
  • Depth is bounded (≤ ~10⁵)
  • You want clean, readable code

⚠️ Avoid recursion when

  • Depth could exceed stack size → use iteration / explicit stack
  • Same subproblems repeat → use memoisation
  • Hot inner loops → rewrite as a loop

6. Practice Problems

Each problem includes a runnable C++ answer. Try yourself first.

  1. Write a recursive function that returns the sum of digits of a non-negative integer.
    একটি অ-ঋণাত্মক পূর্ণসংখ্যার অঙ্কগুলোর যোগফল রিকার্শন দিয়ে বের করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a1.cpp
    #include <bits/stdc++.h>
    using namespace std;
    int ds(int n) { return n == 0 ? 0 : n%10 + ds(n/10); }
    int main() { cout << ds(12345); }
  2. Recursively check whether a string is a palindrome (case-sensitive).
    একটি স্ট্রিং palindrome কিনা — রিকার্শন দিয়ে যাচাই করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a2.cpp
    #include <bits/stdc++.h>
    using namespace std;
    bool pal(const string& s, int i, int j) {
        if (i >= j) return true;
        return s[i] == s[j] && pal(s, i+1, j-1);
    }
    int main() {
        string s = "madam";
        cout << (pal(s, 0, s.size()-1) ? "yes" : "no");
    }
  3. Print all subsets of {1, 2, 3} using recursion.
    {1,2,3}-এর সব subset রিকার্শন দিয়ে প্রিন্ট করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a3.cpp
    #include <bits/stdc++.h>
    using namespace std;
    vector<int> cur;
    void go(vector<int>& a, int i) {
        if (i == (int)a.size()) {
            cout << "{ ";
            for (int x : cur) cout << x << " ";
            cout << "}\n"; return;
        }
        go(a, i+1);
        cur.push_back(a[i]);
        go(a, i+1);
        cur.pop_back();
    }
    int main() { vector<int> a = {1,2,3}; go(a, 0); }
  4. Recursive Euclidean GCD: gcd(a, b) = gcd(b, a % b) with base gcd(a, 0) = a.
    Euclidean GCD অ্যালগরিদমটি রিকার্শন দিয়ে লিখুন।
    ✨ Show Answer (উত্তর দেখুন)
    a4.cpp
    #include <bits/stdc++.h>
    using namespace std;
    long long gcd(long long a, long long b) {
        return b == 0 ? a : gcd(b, a % b);
    }
    int main() { cout << gcd(462, 1071); }
  5. Print the binary representation of a positive integer using recursion (no bitset).
    একটি ধনাত্মক পূর্ণসংখ্যার binary রূপ রিকার্শন দিয়ে প্রিন্ট করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a5.cpp
    #include <bits/stdc++.h>
    using namespace std;
    void bin(int n) {
        if (n == 0) return;
        bin(n / 2);
        cout << (n % 2);
    }
    int main() { bin(42); }
  6. Recursively compute x^n in O(log n) using fast power.
    x^n O(log n) ফাস্ট পাওয়ার রিকার্শন দিয়ে লিখুন।
    ✨ Show Answer (উত্তর দেখুন)
    a6.cpp
    #include <bits/stdc++.h>
    using namespace std;
    long long pw(long long x, long long n) {
        if (n == 0) return 1;
        long long h = pw(x, n / 2);
        return (n & 1) ? h * h * x : h * h;
    }
    int main() { cout << pw(2, 10); }
  7. Count the number of ways to climb n stairs taking 1 or 2 steps at a time (recursive, no DP).
    n ধাপের সিঁড়ি ১ বা ২ ধাপ করে কতভাবে ওঠা যায় — রিকার্শন দিয়ে।
    ✨ Show Answer (উত্তর দেখুন)
    a7.cpp
    #include <bits/stdc++.h>
    using namespace std;
    long long ways(int n) {
        if (n <= 1) return 1;
        return ways(n-1) + ways(n-2);
    }
    int main() { cout << ways(10); }
  8. Without using a loop, recursively print numbers from 1 to n, then from n to 1.
    কোনো loop ছাড়া রিকার্শন দিয়ে 1 থেকে n এবং তারপর n থেকে 1 প্রিন্ট করুন।
    ✨ Show Answer (উত্তর দেখুন)
    a8.cpp
    #include <bits/stdc++.h>
    using namespace std;
    void go(int i, int n) {
        if (i > n) return;
        cout << i << " ";   // down → up
        go(i+1, n);
        cout << i << " ";   // up → down on the way back
    }
    int main() { go(1, 5); }

Summary — Module 04

Recursion is mathematical induction translated into code: a base case + a step that assumes the smaller case is solved. The call stack stores one frame per active call. Naive recursion can repeat work — that observation will become memoisation and then dynamic programming.

রিকার্শন আসলে গাণিতিক ইনডাকশনের কোড-রূপ। base case + recursive step লিখতে পারলেই কাজ শেষ। তবে naive recursion একই কাজ বারবার করে — এই পর্যবেক্ষণ থেকেই DP জন্ম নিয়েছে (Module 34)।

Next Module → Solving Recurrences: Master Theorem & Beyond — recursion-এর running time হিসেব করার ফর্মুলা।