- Array
- Generate Subbarray
- Unique Pair
- Big Integers
- Addition
- Bit Masking
- Arithmetic
- Bit Operation
- Even Odd
- Power
- Swap
- Graph
- Combinatorics
- Binomial Coeefficient
- Birthday Paradox
- Catalan Number
- Number of digit in factorial
- Zero at end of factorial
- Prime
- Is Prime
- Prime Factorization
- Segmented Sieve
- Sieve of Eratosthenes
- Chinese Remainder
- Divisors
- Euler Totient
- Exponentiation
- Euclidean
- Extended Euclidean
- Matrix Exponentiation
- Modulo Arithmetic
- Fast Multiplication
- MMI
- Combinatorics
- Number Theory
- Shortestpath BFS
- Searching
- Lower-Upper Bound
- Sorting
- Inversion Count
- Is Sorted
-
Bit Manipulation
-
Even Odd
-
Time Limit
-
Sum
-
Reverse VS Sort
-
Binary Search
The recursive calls happen first, and any operations are performed after all recursive calls have returned.
void forwardRecursion(int n) {
if (n == 0) {
return;
}
forwardRecursion(n - 1);
cout << n << " ";
}
// 1 2 3 4 5 6 7 8 9 10The operations are performed before the recursive calls. Each recursive call processes its action and then makes the next recursive call.
void backwardRecursion(int n) {
if (n == 0) {
return;
}
cout << n << " ";
backwardRecursion(n - 1);
}
// 10 9 8 7 6 5 4 3 2 1In recursion, each function call is pushed onto the stack. When a function finishes, it's popped off the stack, and execution returns to the previous function call.
1. Function Call: Each recursive call adds a new frame to the stack, containing:
- Local variables
- Parameters
- Return address (where to resume after the function ends)
2. Base Case: When the base case is hit, the recursion stops — the function returns, and the stack starts unwinding.
3. Stack Unwinding: As each function returns, its frame is popped off the stack, and the result is passed back to the previous frame.
For sum of N integer, suppose you input num = 3. The recursive function works like this:
sum(3)is called → Pushsum(3)onto the stacksum(2)is called → Pushsum(2)onto the stacksum(1)is called → Pushsum(1)onto the stacksum(0)is called → Pushsum(0)onto the stack At this point, the stack looks like this:
| sum(0) |
| sum(1) |
| sum(2) |
| sum(3) | <-- Top of the stack🛑 Base Case Reached:
sum(0)returns 0, and the stack starts unwinding.
🔓 Stack Unwinding:
sum(1)pops from the stack and returns1.sum(2)pops from the stack and returns2 + 1 = 3.sum(3)pops from the stack and returns3 + 3 = 6.
A function makes more than one recursive call to itself. This creates a recursive tree-like structure where each function call branches into multiple sub-calls.
Optimize Tree Recursion:
- Memoization
- Tabulation
- Iteration
Forward Recursion:
forwardPrint(3)
|
forwardPrint(2)
|
forwardPrint(1)
|
forwardPrint(0) <- Base case
|
Print(1)
|
Print(2)
|
Print(3)Backward Recursion:
backwardPrint(3)
|
Print(3)
|
backwardPrint(2)
|
Print(2)
|
backwardPrint(1)
|
Print(1)
|
backwardPrint(0) <- Base caseA subsequence of a sequence (string, array, list, etc.) is a sequence that can be derived from the original sequence by deleting zero or more elements without changing the relative order of the remaining elements.
- Elements must preserve order (cannot rearrange).
- You may delete some elements or none at all.
Example
- All possible subsequences of
A = [3, 1, 2]is[](empty subsequence – always included),[3],[1],[2],[3, 1],[3, 2],[1, 2],[3, 1, 2] - All Possible subsequence of
abcis""(empty string)"a", "b", "c","ab", "ac", "bc","abc" - Total subsequences = 2^n (where
n = length of sequence).
Applications of Subsequences in DSA
- Longest Common Subsequence (LCS) – used in diff tools, DNA sequencing, spell checkers.
- Longest Increasing Subsequence (LIS) – used in dynamic programming problems.
- Subsequence Sum Problems – like subset sum, knapsack problem.
- String Matching – checking if one string is a subsequence of another.
- Substring: Continuous part of the string/array.
"ab"is a substring of"abc"."ac"not a substring (because not continuous).
- Subsequence: Not necessarily continuous, but order must be preserved.
"ac"is a subsequence of"abc".
For a sequence s of length n, at index i you always have two choices:
- Exclude
s[i]from the current subsequence - Include
s[i]in the current subsequence
Exploring both choices at every index creates a full binary recursion tree with 2^n leaves (each leaf = one subsequence) with height n = s.size()
Recursion Tree:
i=0, path=""
/ \
exclude 'a' include 'a'
i=1, "" i=1, "a"
/ \ / \
excl 'b' incl 'b' excl 'b' incl 'b'
i=2,"" i=2,"b" i=2,"a" i=2,"ab"
/ \ / \ / \ / \
"" "c" "b" "bc" "a" "ac" "ab" "abc"
Code Implementation:
#include <bits/stdc++.h>
using namespace std;
void generateSubsequences(string s, string current, int index) {
if (index == s.size()) {
cout << "\"" << current << "\"" << endl; // print subsequence
return;
}
// Choice 1: Exclude current character
generateSubsequences(s, current, index + 1);
// Choice 2: Include current character
generateSubsequences(s, current + s[index], index + 1);
}
int main() {
string s = "abc";
cout << "All subsequences of \"" << s << "\":" << endl;
generateSubsequences(s, "", 0);
return 0;
}-
Base case:
if (index == s.size())— we've decided for every character; current is a complete subsequence → print and return. -
Exclude branch: call the function with
index+1but do not changecurrent. -
Include branch: call with
current + s[index]andindex+1— this appends the current character to the subsequence.
Because you do exclude first then include, the printed order follows that traversal (empty subsequence first, then subsequences that include later characters first, etc.).
Call Trace:
Root: (0, "")
- Exclude
a→(1, "")- Exclude
b→(2, "")- Exclude
c→(3, "") print""` - Include
c→(3, "c") print"c"`
- Exclude
- Include
b→(2, "b")- Exclude
c→(3, "b") print"b"` - Include
c→(3, "bc") print"bc"`
- Exclude
- Exclude
- Include
a→(1, "a")- Exclude
b→(2, "a")- Exclude
c→(3, "a") print"a"` - Include
c→(3, "ac") print"ac"`
- Exclude
- Include
b→(2, "ab")- Exclude
c→(3, "ab")print"ab" - Include
c→(3, "abc")print"abc"
- Exclude
- Exclude
""
"c"
"b"
"bc"
"a"
"ac"
"ab"
"abc"
Optimized recursive version (avoid copies)
Two small changes yield much better performance:
- Pass
sasconst string&(no copying). - Keep
currentas a singlestringpassed by reference and push/pop characters (backtracking) — avoids making a new string at every call.
#include <bits/stdc++.h>
using namespace std;
void generateSubsequences(const string &s, string ¤t, int index) {
if (index == (int)s.size()) {
cout << "\"" << current << "\"\n";
return;
}
// Exclude s[index]
generateSubsequences(s, current, index + 1);
// Include s[index]
current.push_back(s[index]);
generateSubsequences(s, current, index + 1);
current.pop_back(); // backtrack
}
int main() {
string s = "abc";
string cur = "";
cout << "All subsequences of \"" << s << "\":" << endl;
generateSubsequences(s, cur, 0);
return 0;
}Why this is better:
sis never copied.current.push_back()andpop_back()mutate a single buffer; no O(n) copies per recursive call.- Time becomes closer to
O(2^n + total_output_size)(still exponential because of output), and memory usage is minimal (stack depthO(n)andcurrentlength<= n).
Time Complexity: O(n * 2^n)
- At each index, we make two recursive calls → one for including, one for excluding.
- So total calls =
2^n. - For each subsequence, printing takes O(n) in the worst case (when subsequence length ≈ n).
Space Complexity = O(n)
- Maximum depth of recursion =
n(stack space). - Space used for storing subsequences (if printing immediately, not storing all) = O(n).
Since there are 2^n subsequences, we can represent each subsequence using a binary mask:
0→ exclude character1→ include character
Example:
For "abc" → length = 3 → total subsequences = 2^3 = 8
000→""001→"c"010→"b"011→"bc"100→"a"101→"ac"110→"ab"111→"abc"
Code Implementation:
#include <bits/stdc++.h>
using namespace std;
int main() {
string s = "abc";
int n = s.size();
int total = 1 << n; // 2^n subsequences
cout << "All subsequences of \"" << s << "\":" << endl;
for (int mask = 0; mask < total; mask++) {
string subseq = "";
for (int i = 0; i < n; i++) {
if (mask & (1 << i)) { // if bit is set, include s[i]
subseq += s[i];
}
}
cout << "\"" << subseq << "\"" << endl;
}
return 0;
}Time Complexity: O(n * 2^n) (same as recursion).
- We loop over all
2^nbitmasks. - For each mask, we check up to
nbits → O(n).
Space Complexity: O(n)
- No recursion stack.
- Only needs a string to build subsequence at each iteration.
- Longest Common Subsequence
- Longest Common Subsequence – LeetCode
- Longest Common Subsequence – WorkAtTech
- Printing Longest Common Subsequence
- Print all LCS sequences
- Longest Increasing Subsequence – GeeksforGeeks
- Longest Increasing Subsequence – LeetCode
- Longest Increasing Subsequence – WorkAtTech
- Longest Increasing Subsequence Size (N log N)
- Print Longest Increasing Subsequence
- Check if one string is subsequence of other
- Check for subsequence
- Check if a String is Subsequence of Other
- Competitive programming: Good Subsets problem
- Longest Common Increasing Subsequence (LCS + LIS)
- Length of the Longest Subsequence That Sums to Target
- Longest Subsequence With Limited Sum
- Largest Divisible Subset
- Longest Common Subsequence Between Sorted Arrays
- Find the Longest Common Subsequence (LCS) in given K permutations
Overflow occurs when a variable is assigned a value greater than its maximum limit, causing the value to wrap around and produce incorrect results.
- An
intis typically32bits, so it can store values between-2^31and2^31 - 1(i.e.,-2147483648to2147483647).
If you assign a value larger than 2147483647 to an int, overflow will occur. For example:
int x = INT_MAX;
cout << "Before overflow: " << x << endl;
x = x + 1;
cout << "After overflow: " << x << endl;Here, adding 1 to 2147483647 causes the variable to wrap around to the minimum value -2147483648.
Underflow does the same but in the opposite direction.
- Use larger data types: consider data types with larger ranges, such as
long long - Check for overflow/underflow conditions manually:
if(x > INT_MAX){
// Handle overflow case
}- Modular arithmatic: you can use modulo
10^9+7as a limit to avoid overflow.
The bit at position 0 represents the least significant bit (LSB), and the bit at position n−1 represents the most significant bit (MSB).
int number = 5;
bool isBitSet = (number & (1 << 0)) != 0;The bit at position 1 represents the least significant bit, and the bit at position n represents the most significant bit.
int number = 5;
bool isBitSet = (number & (1 << (1 - 1))) != 0;- count the number of even number between range [a, b]: $$ \left\lfloor \frac{b}{2} \right\rfloor - \left\lfloor \frac{a-1}{2} \right\rfloor $$ count the number of odd number between range [a, b]: $$ (b - a + 1) - \left( \left\lfloor \frac{b}{2} \right\rfloor - \left\lfloor \frac{a-1}{2} \right\rfloor \right) $$
- $$ {even}^{even}={even} $$
- $$ {odd}^{odd}={odd} $$
- $$ {even}+{even}={even} $$
- $$ {odd}+{odd}={even} $$
- $$ {even}+{odd}={odd} $$
| Input Size (n) | Allowed Time Complexity | Operation Limit ((\approx 10^8) per second) |
|---|---|---|
| $ \ n\leq10^6 \ $ | $ \ O(n) \ $, $ \ O(nlog n) \ $ | Up to 10 million iterations (linear and log-linear) |
| $ \ n\leq10^5 \ $ | $ \ O(n) \ $, $ \ O(nlog n) \ $, $ \ O({n}^{2}) \ $ | Up to 10 billion iterations (quadratic) |
| $ \ n\leq10^4 \ $ | $ \ O(\text{n}^\text{2}) \ $, $ \ O(n\sqrt{n}) \ $ | Quadratic and sub-quadratic operations |
| $ \ n\leq10^3 \ $ | $ \ O(\text{n}^{3}) \ $ | Cubic operations allowed |
| $ \ n\leq100 \ $ | $ \ O({2}^{n}) \ $, $ \ O(n!) \ $ | Exponential operations allowed |
- sum of integers in the range [a,b]: $$ \frac{b-a+1}{2} \times (a + b) $$
- time complexity is O(n)
- doesn't consider the original order
- time complexity is O(nlogn)
- changes order based on sorting criteria
- Sort the array
- Define search space
- Handle corner cases
- Conditional Check
- Function building
- Update search space
- Extract solution