Catalan Numbers — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Each task ships with starter code or a precise spec in all three languages. Implement the logic and validate against the evaluation criteria. Always test against a brute-force oracle on small inputs — enumerate balanced parentheses (or binary trees) directly and confirm the count equals
C_n.
Beginner Tasks (5)¶
Task 1 — Catalan via the recurrence¶
Problem. Implement catalanDP(n) returning C_n using the convolution recurrence C_{n+1} = Σ_{i=0}^{n} C_i · C_{n-i} with C_0 = 1, in O(n²).
Input / Output spec. - Read one integer n (0 ≤ n ≤ 30). - Print C_n.
Constraints. - Use int64; valid for n ≤ 35 before overflow. - Build a table c[0..n]; the convolution for c[k] uses indices summing to k-1.
Hint. c[k] = Σ_{i=0}^{k-1} c[i]·c[k-1-i].
Starter — Go.
package main
import "fmt"
func catalanDP(n int) int64 {
// TODO: build c[0..n] via convolution
return 0
}
func main() {
var n int
fmt.Scan(&n)
fmt.Println(catalanDP(n))
}
Starter — Java.
import java.util.*;
public class Task1 {
static long catalanDP(int n) {
// TODO
return 0;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(catalanDP(sc.nextInt()));
}
}
Starter — Python.
import sys
def catalan_dp(n):
# TODO
return 0
if __name__ == "__main__":
print(catalan_dp(int(sys.stdin.read().split()[0])))
Evaluation. Outputs 1, 1, 2, 5, 14, 42, 132 for n = 0..6; matches the closed form.
Task 2 — Catalan via the closed form¶
Problem. Implement catalanClosed(n) = C(2n,n)/(n+1) building the binomial multiplicatively (interleave * and /) so intermediates stay small. O(n).
Input / Output spec. - Read n (0 ≤ n ≤ 30). Print C_n.
Constraints. No precomputed factorials; build C(2n,n) step by step.
Hint. result = 1; for i in 0..n-1: result = result*(2n-i)/(i+1); return result/(n+1).
Starter — Go.
package main
import "fmt"
func catalanClosed(n int) int64 {
// TODO: multiplicative binomial, then / (n+1)
return 0
}
func main() {
var n int
fmt.Scan(&n)
fmt.Println(catalanClosed(n))
}
Starter — Java.
import java.util.*;
public class Task2 {
static long catalanClosed(int n) {
// TODO
return 0;
}
public static void main(String[] args) {
System.out.println(catalanClosed(new Scanner(System.in).nextInt()));
}
}
Starter — Python.
import sys
def catalan_closed(n):
# TODO
return 0
if __name__ == "__main__":
print(catalan_closed(int(sys.stdin.read().split()[0])))
Evaluation. Agrees with Task 1 for all n ≤ 30.
Task 3 — Brute-force balanced-parentheses oracle¶
Problem. Implement countBalanced(n) that counts balanced strings of n pairs by recursive enumeration, and confirm countBalanced(n) == C_n for n = 0..8.
Input / Output spec. - Read n (0 ≤ n ≤ 12). Print the count.
Constraints. Enumeration only — exponential, but a correctness oracle for the formulas.
Hint. Track (open_left, close_left, balance); place ) only when balance > 0.
Starter — Go.
package main
import "fmt"
func countBalanced(n int) int64 {
var rec func(o, c, bal int) int64
rec = func(o, c, bal int) int64 {
// TODO
return 0
}
return rec(n, n, 0)
}
func main() {
var n int
fmt.Scan(&n)
fmt.Println(countBalanced(n))
}
Starter — Java.
import java.util.*;
public class Task3 {
static long rec(int o, int c, int bal) {
// TODO
return 0;
}
public static void main(String[] args) {
int n = new Scanner(System.in).nextInt();
System.out.println(rec(n, n, 0));
}
}
Starter — Python.
import sys
sys.setrecursionlimit(10000)
def count_balanced(n):
def rec(o, c, bal):
# TODO
return 0
return rec(n, n, 0)
if __name__ == "__main__":
print(count_balanced(int(sys.stdin.read().split()[0])))
Evaluation. Matches C_n for every n ≤ 8 (1, 1, 2, 5, 14, 42, 132, 429, 1430).
Task 4 — Catalan number mod a prime¶
Problem. Implement catalanMod(n, p) returning C_n mod p using factorials and a Fermat modular inverse for /(n+1). O(n).
Input / Output spec. - Read n and prime p (0 ≤ n ≤ 10^5, 2n < p). Print C_n mod p.
Constraints. Use a modular inverse; integer division of the residue is a wrong answer.
Hint. C_n = fact[2n]·inv(n!)·inv((n+1)!) mod p; one Fermat inverse plus a downward sweep builds all inverse factorials.
Starter — Go.
package main
import "fmt"
func powMod(a, e, m int64) int64 {
// TODO: binary exponentiation
return 0
}
func catalanMod(n int, p int64) int64 {
// TODO: factorials + inverse factorials
return 0
}
func main() {
var n int
var p int64
fmt.Scan(&n, &p)
fmt.Println(catalanMod(n, p))
}
Starter — Java.
import java.util.*;
public class Task4 {
static long powMod(long a, long e, long m) { /* TODO */ return 0; }
static long catalanMod(int n, long p) { /* TODO */ return 0; }
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println(catalanMod(sc.nextInt(), sc.nextLong()));
}
}
Starter — Python.
import sys
def catalan_mod(n, p):
# TODO: pow(x, p-2, p) for the inverse
return 0
if __name__ == "__main__":
n, p = map(int, sys.stdin.read().split())
print(catalan_mod(n, p))
Evaluation. For p = 1_000_000_007: catalanMod(10, p) == 16796, catalanMod(100, p) == 558488487. Assert (n+1)·C_n ≡ C(2n,n) (mod p).
Task 5 — Generate all balanced parentheses¶
Problem. Implement generate(n) returning every balanced string of n pairs (LeetCode 22). Confirm there are exactly C_n.
Input / Output spec. - Read n (1 ≤ n ≤ 8). Print the count and the strings.
Constraints. Backtracking: add ( while open < n, add ) while close < open.
Hint. The count returned must equal C_n from Task 1.
Starter — Go.
package main
import "fmt"
func generate(n int) []string {
// TODO: backtracking
return nil
}
func main() {
var n int
fmt.Scan(&n)
g := generate(n)
fmt.Println(len(g), g)
}
Starter — Java.
import java.util.*;
public class Task5 {
static List<String> generate(int n) {
// TODO
return new ArrayList<>();
}
public static void main(String[] args) {
List<String> g = generate(new Scanner(System.in).nextInt());
System.out.println(g.size() + " " + g);
}
}
Starter — Python.
import sys
def generate(n):
# TODO
return []
if __name__ == "__main__":
g = generate(int(sys.stdin.read().split()[0]))
print(len(g), g)
Evaluation. len(generate(n)) == C_n for n = 1..8; every string is balanced.
Intermediate Tasks (5)¶
Task 6 — Count unique binary search trees¶
Problem. Return the number of structurally distinct BSTs storing keys 1…n (LeetCode 96) via the Catalan DP dp[k] = Σ dp[root-1]·dp[k-root]. Confirm it equals C_n. - Provide starter code in all three languages. - Constraints. 1 ≤ n ≤ 19 (fits int64). Compare to catalanClosed(n).
Task 7 — Catalan table mod p, O(N)¶
Problem. Precompute C_0…C_N mod p in O(N) total using factorial / inverse-factorial tables. Answer Q queries C_n in O(1) each. - Provide starter code in all three languages. - Constraints. N ≤ 10^6, Q ≤ 10^6, p prime with 2N < p. Reject n > N.
Task 8 — Count triangulations of a polygon¶
Problem. Given the number of sides s of a convex polygon (s ≥ 3), return the number of triangulations C_{s-2}. Verify a pentagon gives 5 and a hexagon gives 14. - Provide starter code in all three languages. - Constraints. 3 ≤ s ≤ 35 (exact, int64). Mind the s-2 index mapping.
Task 9 — Validate the Catalan identities¶
Problem. Given n and prime p, compute C_n by the division form and the subtraction form C(2n,n) − C(2n,n+1) (mod p) and assert they agree, and also assert (n+1)·C_n ≡ C(2n,n) (mod p). - Provide starter code in all three languages. - Constraints. 0 ≤ n ≤ 10^5, 2n < p. Both forms must match for all tested n.
Task 10 — Monotonic lattice paths below the diagonal¶
Problem. Count monotonic paths in an n × n grid from (0,0) to (n,n) that never rise above the main diagonal (only right/up moves, staying weakly below). The answer is C_n. Implement a DP paths[i][j] and confirm against catalanClosed(n). - Provide starter code in all three languages. - Constraints. 1 ≤ n ≤ 30. The DP fills only cells with j ≤ i.
Advanced Tasks (5)¶
Task 11 — Fuss–Catalan (k-ary trees)¶
Problem. Compute the number of k-ary trees with n internal nodes mod p: (1/((k-1)n+1))·C(kn, n). Verify k = 2 reproduces the ordinary Catalan numbers. - Provide starter code in all three languages. - Constraints. 1 ≤ k ≤ 5, 0 ≤ n ≤ 10^5, kn < p. Use precomputed factorials.
Task 12 — Narayana numbers and their sum¶
Problem. Compute N(n,k) = (1/n)·C(n,k)·C(n,k-1) for all k, and verify Σ_{k=1}^{n} N(n,k) = C_n (the Narayana refinement of Catalan). - Provide starter code in all three languages. - Constraints. 1 ≤ n ≤ 10^4. Exact (big int) or mod p; the row sum must equal C_n.
Task 13 — Exponent tower of the answer count? No — large-n modular pipeline¶
Problem. Build a service that, given a stream of up to 10^6 queries n_i (0 ≤ n_i ≤ 10^6), answers C_{n_i} mod p in O(1) per query after a single O(10^6) precompute. Measure total time. - Provide starter code in all three languages. - Constraints. p = 998244353 (NTT-friendly). Precompute factorials to 2·10^6. Assert each query bound.
Task 14 — Count non-crossing chord diagrams¶
Problem. Given 2n points on a circle, count the ways to connect them with n non-crossing chords. The answer is C_n. Implement via the convolution DP (a chord from point 1 splits the rest into two independent arcs) and verify against catalanClosed(n). - Provide starter code in all three languages. - Constraints. 0 ≤ n ≤ 30. The split is Σ_i C_i·C_{n-1-i} — re-derive the Catalan recurrence from the geometry.
Task 15 — Stack-sortable permutations (231-avoidance)¶
Problem. Given n, count permutations of 1…n that are stack-sortable (avoid the pattern 231). The answer is C_n. Implement a brute-force checker (try a single-stack sort) for n ≤ 9 and confirm the count equals C_n. - Provide starter code in all three languages. - Constraints. 1 ≤ n ≤ 9 for the brute-force; cross-check with catalanClosed(n).
Benchmark Task¶
Compare performance across all 3 languages: precompute factorials and answer
10^6Catalan-mod-p queries.
Go¶
package main
import (
"fmt"
"time"
)
const MOD = 998244353
func main() {
N := 1_000_000
sz := 2*N + 2
fact := make([]int64, sz)
invf := make([]int64, sz)
start := time.Now()
fact[0] = 1
for i := 1; i < sz; i++ {
fact[i] = fact[i-1] * int64(i) % MOD
}
// invf[sz-1] via Fermat, then sweep down
inv := int64(1)
{
a, e := fact[sz-1], int64(MOD-2)
a %= MOD
r := int64(1)
for e > 0 {
if e&1 == 1 {
r = r * a % MOD
}
a = a * a % MOD
e >>= 1
}
inv = r
}
invf[sz-1] = inv
for i := sz - 1; i > 0; i-- {
invf[i-1] = invf[i] * int64(i) % MOD
}
var sum int64
for n := 0; n <= N; n++ {
sum += fact[2*n] * invf[n] % MOD * invf[n+1] % MOD
}
fmt.Printf("checksum=%d in %v\n", sum%MOD, time.Since(start))
}
Java¶
public class Benchmark {
static final long MOD = 998244353L;
public static void main(String[] args) {
int N = 1_000_000, sz = 2 * N + 2;
long[] fact = new long[sz], invf = new long[sz];
long start = System.nanoTime();
fact[0] = 1;
for (int i = 1; i < sz; i++) fact[i] = fact[i - 1] * i % MOD;
long a = fact[sz - 1] % MOD, e = MOD - 2, r = 1;
while (e > 0) { if ((e & 1) == 1) r = r * a % MOD; a = a * a % MOD; e >>= 1; }
invf[sz - 1] = r;
for (int i = sz - 1; i > 0; i--) invf[i - 1] = invf[i] * i % MOD;
long sum = 0;
for (int n = 0; n <= N; n++) sum += fact[2 * n] * invf[n] % MOD * invf[n + 1] % MOD;
long elapsed = System.nanoTime() - start;
System.out.printf("checksum=%d in %.3f ms%n", sum % MOD, elapsed / 1_000_000.0);
}
}
Python¶
import time
MOD = 998244353
def main():
N = 1_000_000
sz = 2 * N + 2
start = time.perf_counter()
fact = [1] * sz
for i in range(1, sz):
fact[i] = fact[i - 1] * i % MOD
invf = [1] * sz
invf[sz - 1] = pow(fact[sz - 1], MOD - 2, MOD)
for i in range(sz - 1, 0, -1):
invf[i - 1] = invf[i] * i % MOD
total = 0
for n in range(N + 1):
total += fact[2 * n] * invf[n] % MOD * invf[n + 1] % MOD
print(f"checksum={total % MOD} in {(time.perf_counter() - start) * 1000:.3f} ms")
if __name__ == "__main__":
main()