卡特兰数在LeetCode刷题中的5种经典应用(附Python代码实现)

卡特兰数(Catalan Numbers)是组合数学中一个既优美又实用的数列,它在计算机科学领域尤其是算法面试中频繁出现。对于准备技术面试的开发者来说,掌握卡特兰数的应用不仅能快速解决特定类型的题目,还能在优化递归和动态规划方案时提供关键思路。本文将聚焦LeetCode高频题型,通过5个经典问题场景,结合Python代码实现,带你深入理解卡特兰数的实战价值。

1. 括号生成问题:构建所有合法组合

LeetCode第22题「括号生成」要求生成所有由n对括号组成的合法组合。例如当n=3时,输出应为["((()))","(()())","(())()","()(())","()()()"]——恰好对应卡特兰数h(3)=5。

递归与卡特兰数的关系

合法括号序列必须满足:

  1. 任意前缀中左括号数量≥右括号数量
  2. 总左括号数=总右括号数=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)

面试中的高频考点

在技术面试中,卡特兰数相关问题通常会考察以下方面:

  1. 模式识别:能否识别问题属于卡特兰数模型

    • 特征:问题可以分解为子问题的乘积和
    • 典型场景:括号、树结构、栈操作
  2. 实现方式

    • 递归解法(直接模拟过程)
    • 动态规划(存储中间结果)
    • 数学公式(直接计算结果)
  3. 复杂度分析

    • 解释为什么解的数量呈卡特兰数增长
    • 分析不同实现方式的时间/空间复杂度
  4. 边界条件处理

    • n=0时的返回值
    • 大数运算时的溢出问题
# 处理大数的卡特兰数计算(使用模数)
MOD = 10**9 + 7

def catalan_mod(n):
    return comb(2*n, n) * pow(n+1, MOD-2, MOD) % MOD

实战技巧与注意事项

  1. 记忆化搜索:当使用递归解法时,添加缓存可以显著提升性能

    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))
    
  2. 避免重复计算:在动态规划实现中,预处理阶乘数组可以优化组合数计算

    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
    
  3. 非递归实现:对于特别大的n,使用递推公式可以避免递归深度限制

    def catalan_iterative(n):
        res = 1
        for i in range(1, n+1):
            res = res * (4*i - 2) // (i + 1)
        return res
    
  4. 常见错误

    • 忘记初始化h(0)=1
    • 整数除法与浮点数精度问题
    • 递归终止条件不正确

在最近的面试中,亚马逊和谷歌都曾考察过基于卡特兰数的变种题。比如要求不仅计算数量,还要枚举所有可能的括号组合或BST结构。这种情况下,理解卡特兰数的生成逻辑比记住公式更重要。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐