Problem
Given a non-negative integer n
, compute the n
-th Catalan number C_n
.
What are catalan numbers
Catalan numbers form a sequence of natural numbers with applications in various combinatorial mathematics problems. They can be defined in several ways and appear in problems involving balanced parentheses, binary search trees, and more. More on wiki.
The (n)-th Catalan number can be given by the formula:
$$ C_n = \frac{1}{n+1}\binom{2n}{n} = \frac{(2n)!}{(n+1)!n!} $$
Some common properties of Catalan numbers include:
- They count the number of expressions containing (n) pairs of correctly matched parentheses. See - Generate Parentheses.
- They count the number of rooted binary trees with (n + 1) leaves.
- They count the number of ways to completely parenthesise (n+1) factors in a product.
Examples
Example 1
|
|
Example 2
|
|
Example 3
|
|
Solution
Method 1 - Naive Recursion
Intuition
Directly apply the recursive convolution definition. This recomputes the same substructures many times, causing explosive growth in calls.
Approach
Base: C_0 = 1
(and optionally C_1 = 1
). For n > 1
sum over splits (i, n-1-i)
multiplying left and right Catalan values.
Recurrence
$$C_n = \sum_{i=0}^{n-1} C_i C_{n-1-i}$$
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(C_n * n)
(super-exponential inn
; asymptoticallyC_n ~ 4^n / (n^{3/2}√π)
), due to massive recomputation. - 🧺 Space complexity:
O(n)
– recursion stack depth.
Method 2 - Top-Down DP (Memoization)
Intuition
Cache intermediate Catalan values so each C_k
is computed once; transforms exponential recursion into polynomial time.
Approach
Use an array/map memo
with sentinel (e.g. -1
or null
). On each call return stored value if present; otherwise compute via recurrence and store.
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(n^2)
–n
states, each summation over up ton
splits. - 🧺 Space complexity:
O(n)
– memo array + recursion stack.
Method 3 - Bottom-Up DP
Intuition
Iteratively build from C_0
upward; avoids recursion overhead and is iterative equivalent of memoized solution.
Approach
Initialize dp[0]=1
, dp[1]=1
. For each i
from 2..n
, accumulate dp[i] += dp[j]*dp[i-1-j]
for j
in [0, i-1]
.
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(n^2)
– double loop over splits. - 🧺 Space complexity:
O(n)
– DP array.
Method 4 - Direct Combinatorial Formula
Intuition
Use multiplicative computation of the binomial coefficient to avoid factorial overflow and then divide by n+1
.
Approach
Iteratively build res = res * (n+i) / i
for i = 1..n
to compute binom(2n, n)
, then C_n = res / (n+1)
.
Code
|
|
|
|
Complexity
- ⏰ Time complexity:
O(n)
– single loop building binomial. - 🧺 Space complexity:
O(1)
– constant auxiliary space.
Method Comparison
Method | Time | Space | Notes |
---|---|---|---|
1 Naive Recursion | O(C_n * n) |
O(n) |
Pedagogical only; explosive growth |
2 Memoized | O(n^2) |
O(n) |
Simple; good for moderate n |
3 Bottom-Up | O(n^2) |
O(n) |
Iterative; avoids recursion overhead |
4 Formula | O(n) |
O(1) |
Fast; beware overflow for large n (use BigInteger if needed) |
Notes
For large n
, use big integer arithmetic to prevent overflow. For example, in Java replace long
with BigInteger
and adjust multiplicative formula accordingly. The DP methods also generalize if you substitute arbitrary semiring operations where Catalan convolution applies.