Article / 文章

LeetCode 第331题:验证二叉树的前序序列化

序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。 例如,上面的二叉树可以被序列化为字符串 "9,3,4,#,#,1,#,#,2,#,6,#,#",其中 # 代表一个空节点。 给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的

📖 文章摘要

本文详细解析LeetCode第331题“验证二叉树的前序序列化”,这是一道中等难度的二叉树和栈问题。文章提供了栈和计数两种解法,包含C#、Python、C++三种语言实现,配有详细的算法分析和性能对比。适合想要提升二叉树和栈操作能力的程序员。

核心知识点: 二叉树、栈、前序遍历、入度出度
难度等级: 中等
推荐人群: 具有基础数据结构知识,想要提升二叉树操作能力的程序员

题目描述

序列化二叉树的一种方法是使用前序遍历。当我们遇到一个非空节点时,我们可以记录下这个节点的值。如果它是一个空节点,我们可以使用一个标记值记录,例如 #。

例如,上面的二叉树可以被序列化为字符串 “9,3,4,#,#,1,#,#,2,#,6,#,#”,其中 # 代表一个空节点。

给定一串以逗号分隔的序列,验证它是否是正确的二叉树的前序序列化。编写一个在不重构树的条件下的可行算法。

示例

示例 1:

输入:preorder = "9,3,4,#,#,1,#,#,2,#,6,#,#"
输出:true

示例1

示例 2:

输入: "1,#"
输出: false

示例 3:

输入: "9,#,#,1"
输出: false

提示

  • 1 <= preorder.length <= 10^4
  • preorder 由以逗号 “,” 分隔的 [0-100] 范围内的整数和 “#” 组成

解题思路

方法一:栈方法

使用栈来模拟树的构建过程。

关键点:

  • 使用栈存储节点状态
  • 处理空节点和非空节点
  • 合并满足条件的节点
  • 验证最终状态

具体步骤:

  1. 将序列按逗号分割
  2. 遍历每个节点
  3. 处理当前节点并更新栈状态
  4. 检查最终栈的状态

时间复杂度:O(n) 空间复杂度:O(n)

方法二:计数法(入度出度)

利用树的入度和出度性质。

关键点:

  • 非空节点提供2个出度和1个入度
  • 空节点提供0个出度和1个入度
  • 除根节点外每个节点都需要1个入度
  • 总入度等于总出度

具体步骤:

  1. 初始化差值diff(出度减入度)为1
  2. 遍历序列,更新差值
  3. 检查过程中差值是否合法
  4. 验证最终差值是否为0

时间复杂度:O(n) 空间复杂度:O(1)

图解思路

栈方法过程分析表

当前节点 栈状态 操作 说明
9 [9] 入栈 非空节点入栈
3 [9,3] 入栈 非空节点入栈
4 [9,3,4] 入栈 非空节点入栈
# [9,3,4,#] 入栈 空节点入栈
# [9,3] 合并 合并完整子树

计数法示意图

节点类型  入度  出度  差值变化
非空节点   1     2    +1
空节点     1     0    -1
最终要求:差值 = 0

代码实现

C# 实现

public class Solution {
    // 方法一:栈方法
    public bool IsValidSerialization(string preorder) {
        Stack<string> stack = new Stack<string>();
        string[] nodes = preorder.Split(',');
        
        foreach (string node in nodes) {
            while (node == "#" && stack.Count > 0 && stack.Peek() == "#") {
                stack.Pop(); // 弹出一个#
                if (stack.Count == 0) return false;
                stack.Pop(); // 弹出一个数字
            }
            stack.Push(node);
        }
        
        return stack.Count == 1 && stack.Peek() == "#";
    }
    
    // 方法二:计数法
    public bool IsValidSerializationCount(string preorder) {
        int diff = 1; // 初始差值为1(根节点)
        string[] nodes = preorder.Split(',');
        
        foreach (string node in nodes) {
            diff--; // 每个节点消耗一个入度
            if (diff < 0) return false;
            if (node != "#") {
                diff += 2; // 非空节点提供两个出度
            }
        }
        
        return diff == 0;
    }
}

Python 实现

class Solution:
    # 方法一:栈方法
    def isValidSerialization(self, preorder: str) -> bool:
        stack = []
        nodes = preorder.split(',')
        
        for node in nodes:
            while node == '#' and stack and stack[-1] == '#':
                stack.pop() # 弹出一个#
                if not stack: return False
                stack.pop() # 弹出一个数字
            stack.append(node)
        
        return len(stack) == 1 and stack[-1] == '#'
    
    # 方法二:计数法
    def isValidSerializationCount(self, preorder: str) -> bool:
        diff = 1 # 初始差值为1(根节点)
        nodes = preorder.split(',')
        
        for node in nodes:
            diff -= 1 # 每个节点消耗一个入度
            if diff < 0: return False
            if node != '#':
                diff += 2 # 非空节点提供两个出度
        
        return diff == 0

C++ 实现

class Solution {
public:
    // 方法一:栈方法
    bool isValidSerialization(string preorder) {
        vector<string> stack;
        stringstream ss(preorder);
        string node;
        
        while (getline(ss, node, ',')) {
            while (node == "#" && !stack.empty() && stack.back() == "#") {
                stack.pop_back(); // 弹出一个#
                if (stack.empty()) return false;
                stack.pop_back(); // 弹出一个数字
            }
            stack.push_back(node);
        }
        
        return stack.size() == 1 && stack.back() == "#";
    }
    
    // 方法二:计数法
    bool isValidSerializationCount(string preorder) {
        int diff = 1; // 初始差值为1(根节点)
        stringstream ss(preorder);
        string node;
        
        while (getline(ss, node, ',')) {
            diff--; // 每个节点消耗一个入度
            if (diff < 0) return false;
            if (node != "#") {
                diff += 2; // 非空节点提供两个出度
            }
        }
        
        return diff == 0;
    }
};

执行结果

C# 实现

  • 执行用时:76 ms
  • 内存消耗:36.8 MB

Python 实现

  • 执行用时:36 ms
  • 内存消耗:15.1 MB

C++ 实现

  • 执行用时:0 ms
  • 内存消耗:6.8 MB

性能对比

语言 执行用时 内存消耗 特点
C# 76 ms 36.8 MB 实现简洁,性能适中
Python 36 ms 15.1 MB 代码最简洁
C++ 0 ms 6.8 MB 性能最优

代码亮点

  1. 🎯 两种不同的解题思路
  2. 💡 巧妙利用入度出度关系
  3. 🔍 完整的边界条件检查
  4. 🎨 代码结构清晰,易于理解

常见错误分析

  1. 🚫 没有处理空序列
  2. 🚫 栈操作顺序错误
  3. 🚫 入度出度计算错误
  4. 🚫 边界条件判断不当

解法对比

解法 时间复杂度 空间复杂度 优点 缺点
栈方法 O(n) O(n) 直观易懂 空间消耗大
计数法 O(n) O(1) 空间效率高 不易理解

相关题目


📖 系列导航

🔥 算法专题合集 - 查看完整合集

📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新至第331题。


💬 互动交流

感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。

如果这篇文章对你有帮助,请:

  • 👍 点个赞,让更多人看到这篇文章
  • 📁 收藏文章,方便后续查阅复习
  • 🔔 关注作者,获取更多高质量算法题解
  • 💭 评论区留言,分享你的解题思路或提出疑问

你的支持是我持续分享的动力!

💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!