Skip to content

Commit 3a6c6f6

Browse files
Add Catalan number algorithm with three implementations
Implements Catalan number calculation using: - Dynamic programming approach - Recursive with memoization - Binomial coefficient formula Includes 18 passing doctests, type hints, and comprehensive documentation.
1 parent 81496db commit 3a6c6f6

File tree

1 file changed

+175
-0
lines changed

1 file changed

+175
-0
lines changed

maths/catalan_number.py

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
"""
2+
Catalan Numbers
3+
4+
Catalan numbers form a sequence of natural numbers that occur in various counting
5+
problems in combinatorics. The nth Catalan number can be expressed directly in
6+
terms of binomial coefficients.
7+
8+
Formula: C(n) = (2n)! / ((n + 1)! * n!)
9+
10+
Alternative formula: C(n) = C(n-1) * 2(2n-1) / (n+1)
11+
12+
Applications:
13+
- Number of different ways n + 1 factors can be completely parenthesized
14+
- Number of different binary search trees with n keys
15+
- Number of paths with n steps east and n steps north that don't cross diagonal
16+
- Number of ways to triangulate a polygon with n + 2 sides
17+
18+
Reference: https://en.wikipedia.org/wiki/Catalan_number
19+
"""
20+
21+
22+
def catalan_number(node_count: int) -> int:
23+
"""
24+
Calculate the nth Catalan number using dynamic programming approach.
25+
26+
Args:
27+
node_count: A non-negative integer representing the position in sequence
28+
29+
Returns:
30+
The nth Catalan number
31+
32+
Raises:
33+
ValueError: If node_count is negative
34+
35+
Examples:
36+
>>> catalan_number(0)
37+
1
38+
>>> catalan_number(1)
39+
1
40+
>>> catalan_number(5)
41+
42
42+
>>> catalan_number(10)
43+
16796
44+
>>> catalan_number(15)
45+
9694845
46+
>>> catalan_number(-1)
47+
Traceback (most recent call last):
48+
...
49+
ValueError: node_count must be a non-negative integer
50+
>>> catalan_number(3.5)
51+
Traceback (most recent call last):
52+
...
53+
ValueError: node_count must be a non-negative integer
54+
"""
55+
if not isinstance(node_count, int) or node_count < 0:
56+
raise ValueError("node_count must be a non-negative integer")
57+
58+
if node_count <= 1:
59+
return 1
60+
61+
# Dynamic programming approach
62+
catalan = [0] * (node_count + 1)
63+
catalan[0] = catalan[1] = 1
64+
65+
for i in range(2, node_count + 1):
66+
for j in range(i):
67+
catalan[i] += catalan[j] * catalan[i - 1 - j]
68+
69+
return catalan[node_count]
70+
71+
72+
def catalan_number_recursive(node_count: int) -> int:
73+
"""
74+
Calculate the nth Catalan number using recursive formula with memoization.
75+
76+
Args:
77+
node_count: A non-negative integer representing the position in sequence
78+
79+
Returns:
80+
The nth Catalan number
81+
82+
Raises:
83+
ValueError: If node_count is negative
84+
85+
Examples:
86+
>>> catalan_number_recursive(0)
87+
1
88+
>>> catalan_number_recursive(1)
89+
1
90+
>>> catalan_number_recursive(5)
91+
42
92+
>>> catalan_number_recursive(10)
93+
16796
94+
>>> catalan_number_recursive(-1)
95+
Traceback (most recent call last):
96+
...
97+
ValueError: node_count must be a non-negative integer
98+
"""
99+
if not isinstance(node_count, int) or node_count < 0:
100+
raise ValueError("node_count must be a non-negative integer")
101+
102+
memo: dict[int, int] = {}
103+
104+
def helper(n: int) -> int:
105+
if n <= 1:
106+
return 1
107+
if n in memo:
108+
return memo[n]
109+
110+
result = 0
111+
for i in range(n):
112+
result += helper(i) * helper(n - 1 - i)
113+
114+
memo[n] = result
115+
return result
116+
117+
return helper(node_count)
118+
119+
120+
def catalan_number_binomial(node_count: int) -> int:
121+
"""
122+
Calculate the nth Catalan number using binomial coefficient formula.
123+
124+
Formula: C(n) = (2n)! / ((n + 1)! * n!)
125+
which equals: C(2n, n) / (n + 1)
126+
127+
Args:
128+
node_count: A non-negative integer representing the position in sequence
129+
130+
Returns:
131+
The nth Catalan number
132+
133+
Raises:
134+
ValueError: If node_count is negative
135+
136+
Examples:
137+
>>> catalan_number_binomial(0)
138+
1
139+
>>> catalan_number_binomial(1)
140+
1
141+
>>> catalan_number_binomial(5)
142+
42
143+
>>> catalan_number_binomial(10)
144+
16796
145+
>>> catalan_number_binomial(15)
146+
9694845
147+
>>> catalan_number_binomial(-1)
148+
Traceback (most recent call last):
149+
...
150+
ValueError: node_count must be a non-negative integer
151+
"""
152+
if not isinstance(node_count, int) or node_count < 0:
153+
raise ValueError("node_count must be a non-negative integer")
154+
155+
if node_count <= 1:
156+
return 1
157+
158+
# Calculate binomial coefficient C(2n, n)
159+
result = 1
160+
for i in range(node_count):
161+
result = result * (2 * node_count - i) // (i + 1)
162+
163+
# Divide by (n + 1)
164+
return result // (node_count + 1)
165+
166+
167+
if __name__ == "__main__":
168+
import doctest
169+
170+
doctest.testmod()
171+
172+
# Print first 15 Catalan numbers
173+
print("First 15 Catalan numbers:")
174+
for i in range(15):
175+
print(f"C({i}) = {catalan_number(i)}")

0 commit comments

Comments
 (0)