题目描述:
题目链接:Pre-Post__牛客网
We are all familiar with pre-order, in-order and post-order traversals of binary trees. A common problem in data structure classes is to find the pre-order traversal of a binary tree when given the in-order and post-order traversals. Alternatively, you can find the post-order traversal when given the in-order and pre-order. However, in general you cannot determine the in-order traversal of a tree when given its pre-order and post-order traversals. Consider the four binary trees below: All of these trees have the same pre-order and post-order traversals. This phenomenon is not restricted to binary trees, but holds for general m-ary trees as well.译文:我们都熟悉二叉树的前序、中序和后序遍历。在数据结构类中,一个常见的问题是在给定, 中序遍历和后序遍历,要求找到二叉树的前序遍历。或者,如果给定了中序遍历和前序遍历,找到后序遍历。然而,通常情况下,当给定树的前序遍历和后序遍历时,你无法确定树的中序遍历。考虑下面的四棵二叉树:所有这些树都具有相同的前序和后序遍历。这种现象不仅适用于二叉树,也适用于一般的m叉树。
输入描述:
Input will consist of multiple problem instances. Each instance will consist of a line of the form m s1 s2, indicating that the trees are m-ary trees, s1 is the pre-order traversal and s2 is the post-order traversal.All traversal strings will consist of lowercase alphabetic characters. For all input instances, 1 <= m <= 20 and the length of s1 and s2 will be between 1 and 26 inclusive. If the length of s1 is k (which is the same as the length of s2, of course), the first k letters of the alphabet will be used in the strings. An input line of 0 will terminate the input.译文: 输入将由多个问题实例组成。每个实例将由 m s1 s2形式的一行组成,表明树是m叉树,s1是前序遍历,s2是后序遍历。所有遍历字符串都将由小写字母字符组成。对于所有输入实例,1 <= m <= 20, s1和s2的长度将在1到26之间(包括26)。如果s1的长度是k(当然,与s2的长度相同),字母表的前k个字母将被用于字符串中。输入行为 0将终止输入。
输出描述:
For each problem instance, you should output one line containing the number of possible trees which would result in the pre-order and post-order traversals for the instance. All output values will be within the range of a 32-bit signed integer. For each problem instance, you are guaranteed that there is at least one tree with the given pre-order and post-order traversals.译文: 对于每个问题实例, 你应该输出一行,由前序遍历和后序遍历构成的,可能的树的数量。所有输出值都在32位有符号整数的范围内。对于每个问题实例,保证至少有一棵树具有给定的前序和后序遍历。
示例
输入
2 abc cba 2 abc bca 10 abc bca 13 abejkcfghid jkebfghicda输出
4 1 45 207352860
题目解读:
这个题目就能把人吓(he)退 ,一连串英文容易读的人发懵。然后输入输出还全是英文描述,又牵扯到 m叉树,一眼看过去,它的难度确定挺大。 题目主要说了一个常识,我们通常 通过中序遍历 加一个 前序或后序遍历,得到一颗确定的树 ,一般遇到前序和后序,由于不确定,我们是不讨论的,这个题它就要剑走偏锋,明知山有虎 偏向虎山行,它就要探索一下 这个不确定,是有多少个不确定 ,而且它还要玩票大的,不仅玩 二叉树,还要玩 多叉树 。 你行,你行,你真行! 输入描述中,没有提到多组输入,但是它的输入示例,明显就是 多组输入,所以我们要用循环接收它的 每组收入, 同时要注意,当 输入的 m 为 0时,要给它跳出循环 。
解题思想:
题目中给了四个例子,它告诉我们,这四个二叉树,虽然长得不一样,但是它们的 前序和后序是完全一样的。 它希望我们由此 获得启发,从而完成它的奇思妙想,满足它对于未知的探索。 那我们就看一遍,发现 确实前序和后序一样,嗯 ,没了 。 这样好像也不行,再读一遍,我们探索一下,前序遍历 和 后序遍历有什么特点, 前序遍历是 根(节点)-- 左(孩子) -- 右(孩子), 后序遍历是 左(孩子) -- 右(孩子) -- 根(节点) ,现在有没有发现什么特点,对咯, 前序遍历 根节点总是冲锋在前,孩子都在它后面,后序遍历 根节点总是 蜗居在后 ,孩子都在它前面 ,那于是我们可以这个为突破口,解这道题。
拿示例中这个例子来说,abc cba ,我们能看出 a 是根节点,bc 是它的孩子节点,这个树很简单,好像说了等于没说, 那咱升级点难度。 我们看下这个前序和后序遍历 abejkd jkebda ,a 就不说了,它是根节点,我们看 前序遍历的 b ,看b 的孩子都有谁, 从后序遍历里面找到 b ,往前看,可以看到 jke (上面说了 ,后序遍历 孩子都在父亲 前面),那通过后序遍历我们就找到了 ,根节点 第一颗完整的 子树(jkeb),然后前序遍历也从b往后数三个 ,来到了 d ,在后序遍历中找到 d ,往前看发现什么也没有(除掉第一颗树里的节点),也就是说 根节点的 第二颗 完整子树 就是个 光杆司令 哈哈哈 。 那这颗有点复杂的 树 长啥样呢 ,把它画出来吧,便于理解。 这就是 a 的两个大孩子。
那到这就结束了吗? 当然没有,第二棵树是个光棍儿,但第一棵树人家 家庭美满,你不解剖一下,怎么知道 家里谁是 父母谁是孩子呢 ! 那就继续 按照前面的思路 分析, 对于第一颗子树,前序遍历是 bejk ,后序遍历是 jkeb ,看前序遍历的 第二个节点,可以发现 jk 都是 e 的孩子 ,然后看 前序遍历的 第三个节点 j , 从后序遍历发现,它也是个光棍儿 ,那我们整理出, 在b 这个家庭中 ,只有e 这么一个好大儿 , 而在e的小家庭中 有两个 孩子 j 和k ,j 和 k 都是两个单身, 好了 ,至此,我们把 b这个家庭解剖完了,同时 a 这个大家庭也就解剖完了,我们的任务就结束了 。 画个图吧 ,
图画好了 ,应该把题意和解题思路都理解的查不多了。 那对于示例中 的 abejkcfghid jkebfghicda ,这个树如何分解,就不难了,我就不分析了哦。
说了这么多,知道如何分解了,但是题目要求输出数字, 我们好像还一直没有提到耶 。是这样的, 那我们不是有个数据一直没有用吗 ,就是 输入的 m ,它是告诉我们 这个树是几叉树的,我们就用它来 计算树的可能性 。 计算之前 ,举个例子 ,你现在手里 有10 件衣服 ,你对象 让你随机拿三件衣服,请问有几种可能性, 那这不就是 组合问题吗 。 我们现在算出一个根节点有多少颗子树 ,这个子树数量 就是 你要随机拿给你对象的衣服数量, m叉树 就是 你目前手里 总共的衣服数量 ,所以这也是个 组合问题 。 这里说一下 组合问题公式:
这就是组合问题的计算公式,所以假如 m 为 13 ,也就是 13 叉树, 根节点下面有三颗 子树,这三颗子树 就有 上面举例中那么多种可能性。 那每颗子树如果它不是 光杆司令,下面还有崽,那它的崽的排布 就和这个同理。
说到这里,这个题的思路已经全部讲解完毕了,还有一个问题 在写代码时 需要注意,就是 二叉树或多叉树的节点 ,它是不重复的 。
代码注释:
import java.util.*;
// 设一个树类,存储每一颗子树 的一家子
class SubTree{
String pre;
String post;
public SubTree(String pre,String post){
this.pre = pre;
this.post = post;
}
}
public class Main{
// 这个函数是具体 计算当前节点 的子树的排布个数
public static int calSubCount(int m,int size){
int sum = 1;
size = size < m-size ? size:m-size;
// 这个用于计算 组合公式的 分子
for(int i=m;i>m-size;i--){
sum *= i;
}
// 这个用于计算 组合公式的 分母
int fac = 1;
for(int i= size;i>0;i--){
fac *= i;
}
sum /= fac;
return sum;
}
// 这个函数 用于具体实现 将当前前序和后序 结果 划分为多个子树
public static List<SubTree> calSubTree(String pre,String post){
int preTreeIndex = 1;
int postTreeIndex = 0;
List<SubTree> list = new ArrayList<>();
while(preTreeIndex < pre.length()){
// 拿到前序遍历的 第二个节点(第一个节点是当前树的根节点)
char ch = pre.charAt(preTreeIndex);
// 在后序遍历中 找到 前序遍历中指定的 节点下标
int findTreeIndex = post.indexOf(ch);
// 计算 划分出的 子树长度,在后序遍历中用找到的下标 - 起始位置 +1
int length = findTreeIndex - postTreeIndex +1;
// 实例化一个 树类 ,把 划分出的子树 前序和后序遍历结果放入
SubTree subTree = new SubTree(
pre.substring(preTreeIndex,preTreeIndex+length),
post.substring(postTreeIndex,postTreeIndex+length)
);
// 把子树加入 到 顺序表中
list.add(subTree);
// 更新前序遍历 和 后序遍历中指针的位置
preTreeIndex += length;
postTreeIndex += length;
}
return list;
}
//这是主函数直接调用的函数,它返回前序和后序遍历 构成的所有可能的树的结果
public static int calTreePossible(int m,String pre,String post){
if(pre.length() == 1)
return 1;
// 这里调用函数 对当前前序和后序遍历 进行子树划分,并将每颗子树一家子放入顺序表
List<SubTree> subTrees = calSubTree(pre,post);
// 这里调用 函数 对当前节点的子树数量 进行排布,返回排布数量
int result = calSubCount(m,subTrees.size());
// 这里是从顺序表中 拿出每一颗树,递归进行 子树划分和子树排布计算
for(SubTree tree: subTrees){
result *= calTreePossible(m,tree.pre,tree.post);
}
return result;
}
public static void main(String[] args){
Scanner scan =new Scanner(System.in);
while(scan.hasNextInt()){
int m = scan.nextInt();
if(m == 0)
break;
String pre =scan.next();
String post = scan.next();
System.out.println(calTreePossible(m,pre,post));
}
}
}