卡特兰数在LeetCode刷题中的5种经典应用(附Python代码实现)
卡特兰数在LeetCode刷题中的5种经典应用(附Python代码实现)
卡特兰数(Catalan Numbers)是组合数学中一个既优美又实用的数列,它在计算机科学领域尤其是算法面试中频繁出现。对于准备技术面试的开发者来说,掌握卡特兰数的应用不仅能快速解决特定类型的题目,还能在优化递归和动态规划方案时提供关键思路。本文将聚焦LeetCode高频题型,通过5个经典问题场景,结合Python代码实现,带你深入理解卡特兰数的实战价值。
1. 括号生成问题:构建所有合法组合
LeetCode第22题「括号生成」要求生成所有由n对括号组成的合法组合。例如当n=3时,输出应为["((()))","(()())","(())()","()(())","()()()"]——恰好对应卡特兰数h(3)=5。
递归与卡特兰数的关系
合法括号序列必须满足:
- 任意前缀中左括号数量≥右括号数量
- 总左括号数=总右括号数=n
这与卡特兰数的定义完美契合。我们可以通过DFS实现:
def generateParenthesis(n):
def backtrack(s, left, right):
if len(s) == 2*n:
res.append(s)
return
if left < n:
backtrack(s+'(', left+1, right)
if right < left:
backtrack(s+')', left, right+1)
res = []
backtrack("", 0, 0)
return res
复杂度优化
- 时间复杂度:O(4^n/√n),由卡特兰数的渐进增长趋势决定
- 空间复杂度:O(n) 递归栈深度
提示:面试中常要求解释为什么解的数量是卡特兰数。可以从二叉树遍历或栈操作角度阐述。
2. 不同的二叉搜索树:结构计数问题
LeetCode第96题「不同的二叉搜索树」需要计算由n个节点组成的异构BST数量。例如n=3时,共有5种结构——这正是h(3)的值。
动态规划解法
定义dp[i]表示i个节点的BST数量,则有递推关系:
def numTrees(n):
dp = [0]*(n+1)
dp[0], dp[1] = 1, 1
for i in range(2, n+1):
for j in range(1, i+1):
dp[i] += dp[j-1] * dp[i-j]
return dp[n]
数学优化
直接使用卡特兰数公式:
from math import comb
def numTrees(n):
return comb(2*n, n) // (n + 1)
3. 栈排序问题:出栈序列的可能性
这是卡特兰数最经典的应用场景。假设有n个元素按顺序入栈,求可能的出栈顺序总数。LeetCode虽然没有直接对应的题目,但这类问题常出现在笔试中。
模拟栈操作
def countStackSequences(n):
res = 0
stack = []
def backtrack(push, pop):
nonlocal res
if pop == n:
res += 1
return
if push < n:
stack.append(push+1)
backtrack(push+1, pop)
stack.pop()
if stack and stack[-1] > 0:
val = stack.pop()
backtrack(push, pop+1)
stack.append(val)
backtrack(0, 0)
return res
数学解法
直接返回卡特兰数第n项:
def countStackSequences(n):
return comb(2*n, n) // (n + 1)
4. 二叉树的中序遍历应用
LeetCode第95题「不同的二叉搜索树 II」要求生成所有可能的BST,这实际上是枚举所有中序遍历为1..n的二叉树结构。
递归构建所有BST
def generateTrees(n):
def build(start, end):
if start > end:
return [None]
trees = []
for i in range(start, end+1):
left = build(start, i-1)
right = build(i+1, end)
for l in left:
for r in right:
root = TreeNode(i)
root.left = l
root.right = r
trees.append(root)
return trees
return build(1, n) if n else []
数量验证
生成的树数量应等于卡特兰数h(n)。例如n=3时,输出列表长度应为5。
5. 凸多边形三角划分问题
虽然LeetCode没有直接对应的题目,但这是卡特兰数的经典应用。将一个凸(n+2)边形用不相交的对角线划分为n个三角形,划分方案数为h(n)。
动态规划实现
def convexPolygonTriangulation(n):
if n <= 1:
return 1
dp = [0]*(n+1)
dp[0], dp[1] = 1, 1
for i in range(2, n+1):
for j in range(i):
dp[i] += dp[j] * dp[i-j-1]
return dp[n]
卡特兰数的计算优化
在实际编码中,我们经常需要快速计算卡特兰数。以下是三种常用方法对比:
| 方法 | 代码示例 | 时间复杂度 | 适用场景 |
|---|---|---|---|
| 递归公式 | h(n) = sum(h(i)*h(n-i-1) for i in 0..n-1) |
O(n²) | 教学理解 |
| 组合数公式 | comb(2n,n)//(n+1) |
O(n) | 编程竞赛 |
| 递推公式 | h(n) = h(n-1)*(4n-2)//(n+1) |
O(n) | 动态规划 |
# 最优实现示例
from math import comb
def catalan(n):
return comb(2*n, n) // (n + 1)
面试中的高频考点
在技术面试中,卡特兰数相关问题通常会考察以下方面:
-
模式识别:能否识别问题属于卡特兰数模型
- 特征:问题可以分解为子问题的乘积和
- 典型场景:括号、树结构、栈操作
-
实现方式:
- 递归解法(直接模拟过程)
- 动态规划(存储中间结果)
- 数学公式(直接计算结果)
-
复杂度分析:
- 解释为什么解的数量呈卡特兰数增长
- 分析不同实现方式的时间/空间复杂度
-
边界条件处理:
- n=0时的返回值
- 大数运算时的溢出问题
# 处理大数的卡特兰数计算(使用模数)
MOD = 10**9 + 7
def catalan_mod(n):
return comb(2*n, n) * pow(n+1, MOD-2, MOD) % MOD
实战技巧与注意事项
-
记忆化搜索:当使用递归解法时,添加缓存可以显著提升性能
from functools import lru_cache @lru_cache(maxsize=None) def catalan_rec(n): if n <= 1: return 1 return sum(catalan_rec(i)*catalan_rec(n-1-i) for i in range(n)) -
避免重复计算:在动态规划实现中,预处理阶乘数组可以优化组合数计算
def precompute_catalan(max_n): fact = [1]*(2*max_n+1) for i in range(1, 2*max_n+1): fact[i] = fact[i-1] * i catalan = [0]*(max_n+1) for n in range(max_n+1): catalan[n] = fact[2*n] // (fact[n] * fact[n+1]) return catalan -
非递归实现:对于特别大的n,使用递推公式可以避免递归深度限制
def catalan_iterative(n): res = 1 for i in range(1, n+1): res = res * (4*i - 2) // (i + 1) return res -
常见错误:
- 忘记初始化h(0)=1
- 整数除法与浮点数精度问题
- 递归终止条件不正确
在最近的面试中,亚马逊和谷歌都曾考察过基于卡特兰数的变种题。比如要求不仅计算数量,还要枚举所有可能的括号组合或BST结构。这种情况下,理解卡特兰数的生成逻辑比记住公式更重要。
更多推荐


所有评论(0)