Files
interview/16-LeetCode Hot 100/对称二叉树.md
yasinshaw 5c1c974e88 docs: 改进LeetCode二叉树题目解题思路
按照改进方案,为以下6个二叉树题目增强了解题思路的详细程度:

1. 二叉树的中序遍历
   - 增加"思路推导"部分,解释递归到迭代的转换
   - 详细说明迭代法的每个步骤
   - 增加执行过程演示和多种解法

2. 二叉树的最大深度
   - 增加"思路推导",对比DFS和BFS
   - 详细解释递归的基准情况
   - 增加多种解法和变体问题

3. 从前序与中序遍历序列构造二叉树
   - 详细解释前序和中序的特点
   - 增加"思路推导",说明如何分治
   - 详细说明切片边界计算

4. 对称二叉树
   - 解释镜像对称的定义
   - 详细说明递归比较的逻辑
   - 增加迭代解法和变体问题

5. 翻转二叉树
   - 解释翻转的定义和过程
   - 详细说明多值赋值的执行顺序
   - 增加多种解法和有趣的故事

6. 路径总和
   - 详细解释路径和叶子节点的定义
   - 说明为什么使用递减而非累加
   - 增加多种解法和变体问题

每个文件都包含:
- 完整的示例和边界条件分析
- 详细的算法流程和图解
- 关键细节说明
- 常见错误分析
- 复杂度分析(详细版)
- 执行过程演示
- 多种解法
- 变体问题
- 总结

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-03-08 21:33:57 +08:00

12 KiB
Raw Blame History

对称二叉树 (Symmetric Tree)

LeetCode 101. 简单

题目描述

给你一个二叉树的根节点 root,检查它是否轴对称。

示例 1:

输入root = [1,2,2,3,4,4,3]
输出true

示例 2:

输入root = [1,2,2,null,3,null,3]
输出false

思路推导

什么是轴对称二叉树?

轴对称: 沿着根节点的中轴线折叠,左右两边完全重合

对称的树:          不对称的树:
    1                   1
   / \                 / \
  2   2               2   2
 / \ / \             \   / \
3  4 4  3             3 3   3
                    (左右子树不对应)

暴力解法分析

思路: 比较左子树和右子树是否镜像对称

观察对称的性质:

  1. 根节点相同(只有一个根)
  2. 左子树的左节点 = 右子树的右节点
  3. 左子树的右节点 = 右子树的左节点

图解:

        1
       / \
      2   2        <- 左右根节点值相同
     / \ / \
    3  4 4  3      <- 3对应34对应4

比较规则:
- 左子树的左(3) vs 右子树的右(3)
- 左子树的右(4) vs 右子树的左(4)

递归思路:

isSymmetric(root):
  return isMirror(root.left, root.right)

isMirror(left, right):
  1. 都为空 → true
  2. 一个为空 → false
  3. 值不同 → false
  4. 值相同 → 检查子节点
     isMirror(left.left, right.right) &&
     isMirror(left.right, right.left)

时间复杂度: O(n) - 每个节点访问一次 空间复杂度: O(h) - h为树高递归栈空间

为什么这样思考?

核心思想:

  1. 分治: 大问题分解为小问题(整棵树对称 → 左右子树镜像)
  2. 镜像定义: 左右对称 = 左子树是右子树的镜像
  3. 递归比较: 从根节点开始,逐层比较对应节点

为什么是 left.left vs right.right?

        1
       / \
      L   R
     / \ / \
   LL LR RL RR

对称要求:
- LL == RR  (左的左 vs 右的右)
- LR == RL  (左的右 vs 右的左)

解题思路

核心思想

将问题转化为:判断两棵树是否互为镜像

详细算法流程

步骤1: 定义递归函数

func isMirror(left, right *TreeNode) bool

步骤2: 处理基准情况

情况1: 两个节点都为空

if left == nil && right == nil {
    return true  // 空树是对称的
}

情况2: 一个节点为空,另一个不为空

if left == nil || right == nil {
    return false  // 不对称
}

关键点: 必须先判断"都为空",再判断"一个为空"

情况3: 两个节点值不同

if left.Val != right.Val {
    return false  // 值不同,不对称
}

步骤3: 递归检查子节点

// 左的左 vs 右的右
// 左的右 vs 右的左
return isMirror(left.Left, right.Right) &&
       isMirror(left.Right, right.Left)

图解:

        1
       / \
      2   2
     / \ / \
    3  4 4  3

检查过程:
├─ 2 == 2 ✓
├─ isMirror(2.left, 2.right)
│  ├─ 3 == 3 ✓
│  ├─ isMirror(3.left, 3.right) → isMirror(nil, nil) → true
│  └─ isMirror(3.right, 3.left) → isMirror(nil, nil) → true
└─ isMirror(2.right, 2.left)
   ├─ 4 == 4 ✓
   ├─ isMirror(4.left, 4.right) → isMirror(nil, nil) → true
   └─ isMirror(4.right, 4.left) → isMirror(nil, nil) → true

关键细节说明

细节1: 为什么判断顺序很重要?

// ❌ 错误顺序
if left == nil || right == nil {
    return true  // 错误!
}

// ✅ 正确顺序
if left == nil && right == nil {
    return true
}
if left == nil || right == nil {
    return false
}

原因:

  • 先判断"都为空"的情况
  • 再判断"一个为空"的情况
  • 顺序错了会导致逻辑错误

细节2: 为什么是 left.Left vs right.Right?

// 镜像对称的对应关系
isMirror(left.Left, right.Right)   // 外侧节点
isMirror(left.Right, right.Left)   // 内侧节点

图解:

        1
       / \
      L   R
     / \ / \
   a   b c   d

对称要求:
a == d  (左的左 vs 右的右)
b == c  (左的右 vs 右的左)

细节3: 为什么不需要检查 leftright 的值是否相同?

// 实际上需要检查!
if left.Val != right.Val {
    return false
}

原因: 对称树的对应节点值必须相同

边界条件分析

边界1: 空树

输入: root = nil
输出: true
处理: 空树是对称的

边界2: 只有根节点

输入: root = [1]
输出: true
处理:
- isMirror(nil, nil)
- 两个都为空返回true

边界3: 左子树为空

输入:
  1
   \
    2

输出: false
处理:
- isMirror(nil, 2)
- 一个为空返回false

边界4: 值不同

输入:
    1
   / \
  2   3

输出: false
处理:
- 2 != 3
- 返回false

边界5: 结构不同

输入:
    1
   / \
  2   2
   \   \
    3   3

输出: false
处理:
- isMirror(2, 2) ✓
- isMirror(2.right, 2.right)
  - isMirror(nil, 3)
  - 一个为空返回false

复杂度分析(详细版)

时间复杂度:

- 每个节点访问一次: O(n)
- 每次访问常数操作: O(1)
- 总计: O(n)

为什么是O(n)?
- 递归遍历所有节点
- 每个节点只比较一次
- 没有重复访问

空间复杂度:

- 递归栈: O(h) - h为树高
  - 最坏情况(链状树): O(n)
  - 最好情况(完全平衡树): O(log n)
- 总计: O(h)

执行过程演示

输入:

    1
   / \
  2   2
 / \ / \
3  4 4  3

执行过程:

调用 isSymmetric(1):
└─ 调用 isMirror(2, 2):
   ├─ 2 == 2 ✓
   ├─ 调用 isMirror(3, 3):
   │  ├─ 3 == 3 ✓
   │  ├─ 调用 isMirror(nil, nil): 返回 true
   │  └─ 调用 isMirror(nil, nil): 返回 true
   │  └─ 返回 true && true = true
   ├─ 调用 isMirror(4, 4):
   │  ├─ 4 == 4 ✓
   │  ├─ 调用 isMirror(nil, nil): 返回 true
   │  └─ 调用 isMirror(nil, nil): 返回 true
   │  └─ 返回 true && true = true
   └─ 返回 true && true = true

最终返回: true

不对称的例子:

输入:
    1
   / \
  2   2
   \   \
    3   3

执行过程:
└─ 调用 isMirror(2, 2):
   ├─ 2 == 2 ✓
   ├─ 调用 isMirror(nil, 3):
   │  ├─ nil != 3
   │  └─ 返回 false
   └─ 返回 false (短路,不再检查右子树)

最终返回: false

代码实现

方法一:递归(推荐)

func isSymmetric(root *TreeNode) bool {
    if root == nil {
        return true
    }
    return check(root.Left, root.Right)
}

func check(left, right *TreeNode) bool {
    // 都为空,对称
    if left == nil && right == nil {
        return true
    }
    // 一个为空,不对称
    if left == nil || right == nil {
        return false
    }
    // 值不同,不对称
    if left.Val != right.Val {
        return false
    }
    // 递归检查子节点
    return check(left.Left, right.Right) &&
           check(left.Right, right.Left)
}

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

方法二:迭代(队列)

func isSymmetric(root *TreeNode) bool {
    if root == nil {
        return true
    }

    queue := []*TreeNode{root.Left, root.Right}

    for len(queue) > 0 {
        left := queue[0]
        right := queue[1]
        queue = queue[2:]

        if left == nil && right == nil {
            continue
        }
        if left == nil || right == nil {
            return false
        }
        if left.Val != right.Val {
            return false
        }

        // 按镜像顺序入队
        queue = append(queue, left.Left, right.Right)
        queue = append(queue, left.Right, right.Left)
    }

    return true
}

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

方法三:迭代(栈)

func isSymmetric(root *TreeNode) bool {
    if root == nil {
        return true
    }

    stack := []*TreeNode{root.Left, root.Right}

    for len(stack) > 0 {
        right := stack[len(stack)-1]
        stack = stack[:len(stack)-1]
        left := stack[len(stack)-1]
        stack = stack[:len(stack)-1]

        if left == nil && right == nil {
            continue
        }
        if left == nil || right == nil {
            return false
        }
        if left.Val != right.Val {
            return false
        }

        // 按镜像顺序入栈
        stack = append(stack, left.Left)
        stack = append(stack, right.Right)
        stack = append(stack, left.Right)
        stack = append(stack, right.Left)
    }

    return true
}

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

常见错误

错误1: 判断顺序错误

错误写法:

func check(left, right *TreeNode) bool {
    if left == nil || right == nil {  // 错误!
        return true
    }
    // ...
}

正确写法:

func check(left, right *TreeNode) bool {
    if left == nil && right == nil {
        return true
    }
    if left == nil || right == nil {
        return false
    }
    // ...
}

原因: 必须先判断"都为空",再判断"一个为空"

错误2: 镜像关系错误

错误写法:

return check(left.Left, right.Left) &&  // 错误!
       check(left.Right, right.Right)

正确写法:

return check(left.Left, right.Right) &&
       check(left.Right, right.Left)

原因: 镜像对称是"左的左 vs 右的右""左的右 vs 右的左"

错误3: 忘记检查值

错误写法:

func check(left, right *TreeNode) bool {
    if left == nil && right == nil {
        return true
    }
    if left == nil || right == nil {
        return false
    }
    // 忘记检查 left.Val != right.Val
    return check(left.Left, right.Right) &&
           check(left.Right, right.Left)
}

正确写法:

func check(left, right *TreeNode) bool {
    if left == nil && right == nil {
        return true
    }
    if left == nil || right == nil {
        return false
    }
    if left.Val != right.Val {
        return false
    }
    return check(left.Left, right.Right) &&
           check(left.Right, right.Left)
}

原因: 对称树的对应节点值必须相同

变体问题

变体1: 判断是否是相同的树

func isSameTree(p *TreeNode, q *TreeNode) bool {
    if p == nil && q == nil {
        return true
    }
    if p == nil || q == nil {
        return false
    }
    if p.Val != q.Val {
        return false
    }
    return isSameTree(p.Left, q.Left) &&
           isSameTree(p.Right, q.Right)
}

变体2: 判断是否是轴对称的N叉树

type Node struct {
    Val      int
    Children []*Node
}

func isSymmetric(root *Node) bool {
    if root == nil {
        return true
    }
    return isMirror(root.Children, root.Children)
}

func isMirror(left, right []*Node) bool {
    if len(left) != len(right) {
        return false
    }

    for i := 0; i < len(left); i++ {
        l, r := left[i], right[len(right)-1-i]
        if !isMirrorNode(l, r) {
            return false
        }
    }

    return true
}

func isMirrorNode(a, b *Node) bool {
    if a == nil && b == nil {
        return true
    }
    if a == nil || b == nil {
        return false
    }
    if a.Val != b.Val {
        return false
    }
    return isMirror(a.Children, b.Children)
}

总结

核心要点:

  1. 镜像定义: 左子树是右子树的镜像
  2. 对应关系: 左的左 vs 右的右,左的右 vs 右的左
  3. 基准情况: 都为空→true一个为空→false值不同→false
  4. 递归检查: 从根节点开始,逐层比较对应节点

易错点:

  • 判断顺序错误(先判断"都为空"
  • 镜像关系错误(应该是 left.Left vs right.Right
  • 忘记检查节点值

推荐写法: 递归法(代码简洁,逻辑清晰)