【编程题 递归】Pre-Post(详细注释 易懂)

题目描述:

题目链接: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));
      }
  }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值