遍历问题

OJ: luogu

题目 ID: P1229

标签: 二叉树

日期: 2025-12-31 12:22

题目解析

本质是思维题,找规律. 本题就是那种思维难道很大,但是编码简单的题目.

核心: 找到规律,单子结点模式的确定

这是一个非常经典的二叉树计数问题。要解决这个问题,我们需要理解为什么“前序遍历 + 后序遍历”无法唯一确定一棵二叉树,以及这种“不确定性”是如何产生的。

核心思路解析

1. 为什么会不确定?

  • 前序遍历 (Preorder):根 -> 左 -> 右
  • 后序遍历 (Postorder):左 -> 右 -> 根

如果一个节点 Root两个孩子(左孩子 L 和 右孩子 R):

  • 前序:Root L... R...
  • 后序:L... R... Root 我们可以清晰地分辨出 L 结束和 R 开始的边界,结构是唯一的。

但是,如果一个节点 Root 只有一个孩子 Child

  • 情况 A(Child 是左孩子):

  • 前序:Root Child...

  • 后序:Child... Root

  • 情况 B(Child 是右孩子):

  • 前序:Root Child...

  • 后序:Child... Root

关键发现:当节点只有一个孩子时,无论是作为左孩子还是右孩子,它的前序和后序遍历序列是完全一样的。 这意味着,每一个“只有一个孩子的节点”,都会带来两种可能性(左或右)。

2. 计算公式

如果我们在整棵树中发现了 个这样的“只有一个孩子的节点”,那么可能的中序遍历总数就是:

(因为每个这样的节点都有 2 种选择,根据乘法原理,总数是 2k2^k)。

3. 如何在字符串中检测这种情况?

我们需要在前序和后序字符串中找到这种特征:

  • 在前序中:A 紧跟着 B (即 AB),表示 AB 的父节点。
  • 在后序中:B 紧跟着 A (即 BA),表示 A 的子树是以 B 结尾的。

判据: 如果在前序遍历中出现了 ... A B ...,且在后序遍历中出现了 ... B A ...,这就意味着 BA唯一直接子节点(或者更准确地说,我们无法区分 A 到底只有左孩子 B 还是只有右孩子 B,但这正是我们需要计数的歧义点)。

反证: 如果 A 有两个孩子 B(左)和 C(右):

  • 前序:... A B ... C ...
  • 后序:... B ... C A ... 此时前序是 AB,但后序是 CABA 在后序中不相邻。

算法步骤

  1. 读取前序字符串 s1 和后序字符串 s2
  2. 初始化答案 ans = 1
  3. 遍历前序字符串 s1,考察每一对相邻字符 s1[i]s1[i+1]
  4. 在后序字符串 s2 中找到这两个字符的位置。
  5. 如果它们在 s2 中也是相邻的(顺序相反,即 s1[i+1]s1[i] 的前一位),说明发现了一个“单子节点”结构。
  6. 每发现一个,ans 乘以 2。
  7. 输出 ans

C++ 代码实现

cpp
#include <iostream>
#include <string>
#include <cstdio>

using namespace std;

int main() {
    string pre, post;
    cin >> pre >> post;

    long long ans = 1;
    
    // 遍历前序字符串,注意范围是 0 到 length-2
    // 因为我们要访问 pre[i] 和 pre[i+1]
    for (int i = 0; i < pre.length() - 1; i++) {
        char root = pre[i];
        char child = pre[i+1];
        
        // 在后序遍历中找到 root 和 child 的位置
        // 实际上我们只需要判断:在 post 中,child 是否紧挨在 root 之前
        
        // 找到 root 在 post 中的位置
        int rootIdx = post.find(root);
        
        // 找到 child 在 post 中的位置
        int childIdx = post.find(child);
        
        // 如果 child 正好在 root 的前一位
        if (rootIdx == childIdx + 1) {
            ans *= 2;
        }
    }

    cout << ans << endl;

    return 0;
}

举例图解验证

输入:

pre: abc
post: cba

  1. i=0: pre[0]='a', pre[1]='b'. (对 ab 关系进行检查)
  • post 中,b 的下标是 1,a 的下标是 2。
  • 2 == 1 + 1,满足条件!ans 变 2。
  • 解释a 只有一个孩子 bb 可以是左或是右。
  1. i=1: pre[1]='b', pre[2]='c'. (对 bc 关系进行检查)
  • post 中,c 的下标是 0,b 的下标是 1。
  • 1 == 0 + 1,满足条件!ans 变 4。
  • 解释b 只有一个孩子 cc 可以是左或是右。
  1. 输出 4。正确。