一天一大 leet(从先序遍历还原二叉树)难度:困难 DAY-18

时间:2022-07-25
本文章向大家介绍一天一大 leet(从先序遍历还原二叉树)难度:困难 DAY-18,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

题目(难度:困难):

我们从二叉树的根节点 root 开始进行深度优先搜索。

在遍历中的每个节点处,我们输出 D 条短划线(其中 D 是该节点的深度),然后输出该节点的值。(如果节点的深度为 D,则其直接子节点的深度为 D + 1。根节点的深度为 0)。

如果节点只有一个子节点,那么保证该子节点为左子节点。

给出遍历输出 S,还原树并返回其根节点 root。

示例

  • 示例 1
    1
   / 
  2   3
 /    / 
4  5  6   7
输入:"1-2--3--4-5--6--7"
输出:[1,2,5,3,4,6,7]
  • 示例 2
        1
      /  
     2    5
    /    /
   3    6
  /    /
 4    7
输入:"1-2--3---4-5--6---7"
输出:[1,2,5,3,null,6,null,4,null,7]
  • 示例 3
       1
      /
    401
    /  
  349  88
  /
 90
输入:"1-401--349---90--88"
输出:[1,401,null,349,88,90]

提示

  1. 原始树中的节点数介于 1 和 1000 之间。
  2. 每个节点的值介于 1 和 10 ^ 9 之间。

抛砖引玉

  • 输入一个字符串
  • 字符串中n个'-'表示n级
  • '---n...---m'表示n和m都是上一级的子集,n是left,m是right,先left后right
  • 字符串顺序:一条线所有的子节点+下一条线所有子节点.....

逻辑

  • 先取出第一个数组作为树的根节点
  • 循环字符串记录'-'的个数,多少个n就记录多少级,根据级数把能得到的树放到数组中
  • 记录level和取本层数据的注意事项:
    • 数字和'-'切换时重置level和value
  • 因为字符串顺序:一条线所有的子节点+下一条线所有子节点.....,以第二层为例:
    • 第一条线循环到结束后可能还会在之后一个有level为2的数据,
    • 那就可以判断这个level是不是已经有了left、right没有的话就给原树增加新的树节点
    • 依次类推,所有后面出现的节点本身都是有依附节点的,最终所有的节点都会追加到起始的根节点
/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {string} S
 * @return {TreeNode}
 */
var recoverFromPreorder = function (S) {
  var level = 0,
    value = 0
  var _result = [new TreeNode(S.split('-')[0])]
  for (let i = 1; i < S.length; i++) {
    if (S[i] == '-') {
      level++
      value = 0
    } else if (level > 0) {
      value = value * 10 + Number(S[i])
      if ((S[i + 1] && S[i + 1] === '-') || i === S.length - 1) {
        var nodeTree = new TreeNode(value)
        if (_result[level - 1].left == null) {
          _result[level - 1].left = nodeTree
        } else {
          _result[level - 1].right = nodeTree
        }
        _result[level] = nodeTree
        level = 0
      }
    }
  }
  return _result[0]
}

其他解法

/**
 * @param {number[]} A
 * @return {number}
 */
var recoverFromPreorder = function (S) {
  const stack = []
  for (let i = 0; i < S.length;) {
    let curLevel = 0 //一个curNode对应一个curLevel
    while (i < S.length && S[i] == '-') { // 避免循环半途中出界
      i++
      curLevel++    // 连字符个数代表level
    }
    const start = i // 记录当前节点值的开始位置
    while (i < S.length && S[i] != '-') {
      i++           // 指针移到当前节点值的结束位置
    }
    const curNode = new TreeNode(S.slice(start, i)) //创建当前节点
    if (stack.length == 0) { // ROOT入栈,不用找父亲,continue
      stack.push(curNode)
      continue
    }
    while (stack.length > curLevel) { // 栈顶不是父亲,栈顶出栈
      stack.pop()                     // 直到栈顶是父亲
    }
    if (stack[stack.length - 1].left) {       // 左儿子已存在
      stack[stack.length - 1].right = curNode // 安排为右儿子
    } else {
      stack[stack.length - 1].left = curNode  // 安排为左儿子
    }
    stack.push(curNode) // 节点肯定要入栈一次
  }
  return stack[0]       // 栈底就是根节点
};
  • 使用递归,一个层级一次递归
  • 使用正则切分除本层的数据和未分离出来的字符串
    • 本层数据直接生成树结构
    • 未分离的数据根据切位的位置,递归进入left和right
/**
 * @param {number[]} A
 * @return {number}
 */
var maxScoreSightseeingPair = function (A) {
  if (!S) return null
  const reg = new RegExp(`(?<=\d)${char}(?=\d)`),
    [root, left, right] = S.split(reg),
    treeNode = new TreeNode(+root);
  treeNode.left = recoverFromPreorder(left, char + '-')
  treeNode.right = recoverFromPreorder(right, char + '-')
  return treeNode
}

菜鸟的自白

  • 开始的思路也是通过正则匹配出来,但是对数据的切分一直错误,才不得不去按部就班的去循环,个人觉得,正则出来起来会更简洁
// 配置 两个数字包含着的层级数[val-left,*-right]
/d-{n}d/g
// 跟 数字 -- 数字 切分字符串
/?<=\d)${char}(?=\d/g