Space Complexity Optimization in Recursive Solutions: Lessons from leetcode-master

The leetcode-master repository treats space complexity optimization in recursive solutions as a first-class concern, employing tail-style recursion with state-carrying parameters, elimination of duplicate branches, and reference-passed mutable containers to reduce auxiliary space from O(2ⁿ) to O(n) or O(log n).

Space complexity optimization in recursive solutions is critical for passing LeetCode constraints and avoiding stack overflow errors. The leetcode-master repository by youngyangyang04 provides comprehensive guidance on minimizing call-stack usage through specific engineering patterns documented across its problem sets and complexity analysis notes.

Core Patterns for Space Complexity Optimization

Tail-Style Recursion with State-Carrying Parameters

Instead of letting each call create new sub-problems, the repository advocates passing the current state forward through parameters. This approach, documented in problems/递归算法的时间与空间复杂度分析.md, ensures only one new stack frame is added per logical step, transforming exponential stack growth into linear depth.

Eliminating Duplicate Recursive Branches

When algorithms recursively call the same sub-problem multiple times, stack depth can explode to O(2ⁿ). The repository demonstrates refactoring such patterns to compute sub-results once, either through memoization or by merging calls into a single tail-recursion, dramatically reducing auxiliary space requirements.

Passing Mutable Containers by Reference

For tree traversals and accumulation problems, the repository consistently passes vector<int>& (C++) or equivalent mutable lists by reference rather than returning new copies. This guarantees each frame holds only a constant-size reference, keeping auxiliary space at O(h) where h is the tree height.

Practical Implementations in leetcode-master

Fibonacci: From Exponential to Linear Stack Depth

The repository contrasts two implementations in problems/递归算法的时间与空间复杂度分析.md. The naive version makes two recursive calls, creating O(2ⁿ) stack frames:

int fibonacci(int i) {
    if (i <= 0) return 0;
    if (i == 1) return 1;
    return fibonacci(i-1) + fibonacci(i-2);   // two recursive calls → O(2ⁿ) stack depth
}

The optimized "版本二" (version 2) uses tail-style recursion with state-carrying parameters, reducing stack depth to O(n) with O(1) per-call memory:

int fibonacci(int first, int second, int n) {
    if (n <= 0) return 0;
    if (n < 3)   return 1;
    if (n == 3)  return first + second;
    return fibonacci(second, first + second, n - 1); // single call → O(n) stack depth
}

Binary Search: O(log n) Stack Space

For divide-and-conquer algorithms, the repository emphasizes that recursion depth equals the number of divisions. In problems/递归算法的时间与空间复杂度分析.md, the binary search implementation demonstrates O(log n) stack complexity:

int binary_search(int arr[], int l, int r, int x) {
    if (r >= l) {
        int mid = l + (r - l) / 2;
        if (arr[mid] == x) return mid;
        if (arr[mid] > x)  return binary_search(arr, l, mid - 1, x);
        return binary_search(arr, mid + 1, r, x);
    }
    return -1;
}

Each recursive call halves the problem size, resulting in maximum stack depth of log₂(n) and O(1) per-frame memory.

Binary Tree Traversal: Reference-Passed Vectors

The problems/二叉树的递归遍历.md file provides templates for tree traversals that optimize space by passing result containers by reference. This pattern ensures each recursive frame uses constant auxiliary space:

class Solution {
public:
    void traversal(TreeNode* cur, vector<int>& vec) {
        if (cur == nullptr) return;          // termination
        vec.push_back(cur->val);              // work for this level
        traversal(cur->left,  vec);           // recurse left
        traversal(cur->right, vec);           // recurse right
    }
    
    vector<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        traversal(root, res);
        return res;                           // only O(h) stack, O(1) per-call extra memory
    }
};

By passing vec as vector<int>&, the algorithm avoids copying the accumulator at each level, maintaining O(h) space complexity where h is the tree height.

Summary

  • Tail-style recursion with state-carrying parameters transforms exponential O(2ⁿ) stack usage into linear O(n) depth by passing accumulated state forward rather than creating new sub-problems.
  • Eliminating duplicate branches through single-call recursion patterns prevents redundant stack frames and reduces auxiliary space complexity from exponential to linear or logarithmic.
  • Passing mutable containers by reference (e.g., vector<int>&) ensures tree traversals and accumulation algorithms use only O(h) stack space with constant per-frame overhead.
  • Depth-aware algorithm selection leverages the logarithmic recursion depth of divide-and-conquer approaches like binary search to guarantee O(log n) auxiliary space.

Frequently Asked Questions

What is the difference between O(2ⁿ) and O(n) stack depth in recursive Fibonacci?

The naive Fibonacci implementation makes two recursive calls per invocation (fibonacci(i-1) + fibonacci(i-2)), creating a binary recursion tree with O(2ⁿ) stack frames in the worst case. The tail-recursion optimization passes the two previous values as parameters, creating a single linear chain of O(n) frames with O(1) memory per call, as demonstrated in problems/递归算法的时间与空间复杂度分析.md.

Why does passing vectors by reference reduce space complexity?

When a recursive function returns a new vector at each level, the compiler must allocate new memory and copy elements for every stack frame, resulting in O(n²) or worse space usage. Passing a vector<int>& reference allows all frames to write to the same memory location, keeping auxiliary space at O(h) where h is the tree height or recursion depth, as shown in problems/二叉树的递归遍历.md.

How does binary search achieve O(log n) auxiliary space?

Binary search divides the problem size in half with each recursive call. Since the recursion depth equals the number of times n can be divided by 2, the maximum stack depth is log₂(n). Each frame uses constant O(1) space for variables like mid, resulting in total auxiliary space of O(log n), documented in problems/递归算法的时间与空间复杂度分析.md.

When should I convert recursive solutions to iterative ones?

According to the leetcode-master repository, you should consider explicit stack iteration when the recursion depth exceeds typical stack limits (approximately 10⁴–10⁵ frames in most environments) or when the algorithm exhibits O(n) depth with large input sizes. While recursion teaches fundamental concepts, the repository notes that iterative "非递归" methods are recommended for production code handling deep trees or linear chains.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →