Skip to main content

Command Palette

Search for a command to run...

Day 19 – Recursion, Backtracking, and Math Logic

Published
4 min read

1. Subset Sum (Leetcode 78: Subsets)

Problem: Return all possible subsets (the power set) of a given array of distinct integers.

Intuition: At each index, we have two choices: include or exclude the current element.

void solve(int i, vector<int>& nums, vector<int>& temp, vector<vector<int>>& ans) {
    if (i == nums.size()) {
        ans.push_back(temp);
        return;
    }
    temp.push_back(nums[i]);
    solve(i + 1, nums, temp, ans);
    temp.pop_back();
    solve(i + 1, nums, temp, ans);
}

vector<vector<int>> subsets(vector<int>& nums) {
    vector<vector<int>> ans;
    vector<int> temp;
    solve(0, nums, temp, ans);
    return ans;
}
// TC: O(2^n) subsets * O(n) per subset = O(n * 2^n)
// SC: O(n) recursion + O(n * 2^n) output

2. Subset Sum 2 (Leetcode 90: Subsets II)

Problem: Like Subset Sum, but input can have duplicates, and result should have unique subsets.

Intuition: Sort array first. If we pick an element, skip all consecutive duplicates in the same recursion level.

void solve(int start, vector<int>& nums, vector<int>& temp, vector<vector<int>>& ans) {
    ans.push_back(temp);
    for (int i = start; i < nums.size(); i++) {
        if (i > start && nums[i] == nums[i - 1]) continue;
        temp.push_back(nums[i]);
        solve(i + 1, nums, temp, ans);
        temp.pop_back();
    }
}

vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    vector<vector<int>> ans;
    vector<int> temp;
    solve(0, nums, temp, ans);
    return ans;
}
// TC: O(2^n)
// SC: O(n * 2^n) output + O(n) recursion

3. Combination Sum 1 (Leetcode 39)

Problem: Given array of candidates (no duplicates), and target. You can reuse elements. Find combinations that sum to target.

Intuition: Backtrack by including current element (can repeat), or move to next. Stop if sum exceeds target.

void dfs(int i, int target, vector<int>& temp, vector<vector<int>>& ans, vector<int>& candidates) {
    if (target == 0) {
        ans.push_back(temp);
        return;
    }
    if (i == candidates.size() || target < 0) return;

    temp.push_back(candidates[i]);
    dfs(i, target - candidates[i], temp, ans, candidates);
    temp.pop_back();

    dfs(i + 1, target, temp, ans, candidates);
}

vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
    vector<vector<int>> ans;
    vector<int> temp;
    dfs(0, target, temp, ans, candidates);
    return ans;
}
// TC: O(2^t) where t = target, since each element can be reused
// SC: O(target) recursion + output

4. Combination Sum 2 (Leetcode 40)

Problem: Each number can be used only once, and array may contain duplicates.

Intuition: Sort the array. At each recursion level, skip duplicates after using one instance of a number.

void dfs(int start, int target, vector<int>& temp, vector<vector<int>>& ans, vector<int>& candidates) {
    if (target == 0) {
        ans.push_back(temp);
        return;
    }

    for (int i = start; i < candidates.size(); ++i) {
        if (i > start && candidates[i] == candidates[i - 1]) continue;
        if (candidates[i] > target) break;
        temp.push_back(candidates[i]);
        dfs(i + 1, target - candidates[i], temp, ans, candidates);
        temp.pop_back();
    }
}

vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
    sort(candidates.begin(), candidates.end());
    vector<vector<int>> ans;
    vector<int> temp;
    dfs(0, target, temp, ans, candidates);
    return ans;
}
// TC: O(2^n)
// SC: O(n) recursion + output

5. Palindrome Partitioning (Leetcode 131)

Problem: Partition a string such that every substring is a palindrome.

Intuition: Backtrack while checking palindromic substrings. If valid, recurse on remaining string.

bool isPalindrome(string& s, int l, int r) {
    while (l < r) {
        if (s[l++] != s[r--]) return false;
    }
    return true;
}

void dfs(int start, string& s, vector<string>& temp, vector<vector<string>>& ans) {
    if (start == s.size()) {
        ans.push_back(temp);
        return;
    }

    for (int end = start; end < s.size(); ++end) {
        if (isPalindrome(s, start, end)) {
            temp.push_back(s.substr(start, end - start + 1));
            dfs(end + 1, s, temp, ans);
            temp.pop_back();
        }
    }
}

vector<vector<string>> partition(string s) {
    vector<vector<string>> ans;
    vector<string> temp;
    dfs(0, s, temp, ans);
    return ans;
}
// TC: O(2^n * n) — exponential for combinations, n for checking palindrome
// SC: O(n) recursion + output

6. Permutation Sequence (Leetcode 60)

Brute-force:

Idea: Generate all permutations using STL or backtracking and return the kth.

string getPermutation(int n, int k) {
    vector<int> nums;
    for (int i = 1; i <= n; i++) nums.push_back(i);
    vector<string> perms;

    do {
        string s;
        for (int i : nums) s += to_string(i);
        perms.push_back(s);
    } while (next_permutation(nums.begin(), nums.end()));

    return perms[k - 1];
}
// TC: O(n! * n)
// SC: O(n!)

Optimal (Math based):

Intuition: Use factorial logic. For each digit position, decide which number it should be.

string getPermutation(int n, int k) {
    vector<int> nums;
    for (int i = 1; i <= n; i++) nums.push_back(i);

    int fact = 1;
    for (int i = 1; i < n; i++) fact *= i;

    k -= 1; // 0-indexed
    string ans = "";

    while (!nums.empty()) {
        int idx = k / fact;
        ans += to_string(nums[idx]);
        nums.erase(nums.begin() + idx);
        if (nums.empty()) break;
        k %= fact;
        fact /= nums.size();
    }
    return ans;
}
// TC: O(n^2) due to erase in vector
// SC: O(n)