剑指offer - 树

Posted muzidaitou

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了剑指offer - 树相关的知识,希望对你有一定的参考价值。

1.重建二叉树

问题描述:

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

function reConstructBinaryTree(pre, vin) {
  // write code here
  if (pre.length == 0 || vin.length == 0) {
    return null;
  }
  var root = pre[0]; //根节点
  var index = vin.indexOf(root); //在vin中找到根节点的索引
  var left = vin.slice(0, index); //左子树
  var right = vin.slice(index + 1); //右子树
  var node = new TreeNode(root); //新建一个二叉树
  node.left = reConstructBinaryTree(pre.slice(1, index + 1), left); //左子树的前序和中序
  node.right = reConstructBinaryTree(pre.slice(index + 1), right); //右子树的前序和中序
  return node;
}

2.二叉树的下一个结点

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

解题思路:

分析二叉树的下一个节点,一共有以下情况:

1.二叉树为空,则返回空;

2.有右子树的,那么下个结点就是右子树最左边的点

3.没有右子树的,也可以分成两类:

  • 是父节点左孩子,那么父节点就是下一个节点
  • 是父节点的右孩子,找他的父节点的父节点的父节点...直到当前结点是其父节点的左孩子位置,下一个结点就是当前结点
/*function TreeLinkNode(x){
    this.val = x;
    this.left = null;
    this.right = null;
    this.next = null;
}*/

function GetNext(pNode) {
  // write code here
  if (pNode === null) return null; //空结点
  var p = null;
  if (pNode.right) {
    //有右子树,则下一个结点在右子树最左边的结点
    p = pNode.right;
    while (p.left !== null) {
      p = p.left;
    }
    return p;
  } else {
    //没有右子树
    p = pNode.next;
    if (p && p.right === pNode) {
      while (p.next && p.next.right === p) {
        p = p.next;
      }
      p = p.next;
    }
    return p;
  }
  return null;
}

3.对称的二叉树

问题描述:

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。空二叉树也是对称的。

解题思路:

空二叉树也是对称的,这是值得注意的点。

然后将二叉树一层一层比较一下

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

function isSymmetrical(pRoot) {
  // write code here
  if (!pRoot) return true; // 注意是true
  return judge(pRoot.left, pRoot.right);
}

function judge(node1, node2) {
  //判断这两个结点以及子节点是否对称
  if (node1 === null && node2 === null) {
    return true;
  } else if (node1 === null || node2 === null) {
    return false;
  }
  if (node1.val !== node2.val) {
    return false;
  } else {
    return judge(node1.left, node2.right) && judge(node1.right, node2.left);
  }
}

4.按之字形顺序打印二叉树

问题描述:

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。空二叉树输出空数组。

输入:
技术图片

输出:
[[8],[10,6],[5,7,9,11]]

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
*/

function Print(pRoot) {
  // write code here
  if (!pRoot) return [];
  const nodes = []; //结点
  const vals = []; //val
  var flag = true; //true代表顺序打印
  nodes.push(pRoot);
  while (nodes.length) {
    var temp = [];
    var len = nodes.length; //这里注意要先把nodes的长度赋值给一个变量,因为下面循环中nodes里会添加新的结点
    for (let i = 0; i < len; i++) {
      var node = nodes.shift(); //每次弹出nodes中第一个结点
      flag === true ? temp.push(node.val) : temp.unshift(node.val);
      if (node.left) {
        nodes.push(node.left);
      }
      if (node.right) {
        nodes.push(node.right);
      }
    }
    flag = !flag;
    vals.push(temp);
  }
  return vals;
}

5.把二叉树打印成多行

问题描述:

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

输入:
技术图片

输出:
[[8],[6,10],[5,7,9,11]]

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

function Print(pRoot) {
  // write code here‘
  if (!pRoot) return [];
  const nodes = [];
  const vals = [];
  nodes.push(pRoot);
  while (nodes.length) {
    var len = nodes.length;
    var temp = [];
    for (let i = 0; i < len; i++) {
      var node = nodes.shift();
      temp.push(node.val);
      if (node.left) {
        nodes.push(node.left);
      }
      if (node.right) {
        nodes.push(node.right);
      }
    }
    vals.push(temp);
  }
  return vals;
}

6.序列化二叉树

问题描述:

请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果 str,重构二叉树。

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */

var arr = [];
function Serialize(pRoot) {
  // write code here
  if (!pRoot) {
    arr.push("#");
  } else {
    arr.push(pRoot.val);
    Serialize(pRoot.left);
    Serialize(pRoot.right);
  }
}
function Deserialize(s) {
  // write code here
  if (arr === null) return null;
  if (arr.length < 1) return null;
  var root = null;
  var temp = arr.shift();
  if (typeof temp === "number") {
    root = new TreeNode(temp);
    root.left = Deserialize(arr);
    root.right = Deserialize(arr);
  }
  return root;
}

7.二叉搜索树的第 k 个结点

题目描述:

给定一棵二叉搜索树,请找出其中的第 k 小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为 4。

解题思路:

二叉搜索树的特性:左子树 < 根节点 < 右子树

故只需要找出二叉搜索树的中序,然后找第 k 个结点

/* function TreeNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
} */
function KthNode(pRoot, k) {
  // write code here
  var arr = []; //中序存储结点
  // 中序函数
  function mid(pRoot) {
    if (!pRoot) return null;
    mid(pRoot.left);
    arr.push(pRoot);
    mid(pRoot.right);
  }
  // 调用中序函数
  mid(pRoot);
  return arr[k - 1];
}

8.数据流中的中位数

问题描述:

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用 Insert()方法读取数据流,使用 GetMedian()方法获取当前读取数据的中位数。

var arr = [];
function Insert(num) {
  // write code here
  arr.push(num);
  var i = arr.length - 1;
  while (i > 0) {
    if (arr[i] < arr[i - 1]) {
      [arr[i], arr[i - 1]] = [arr[i - 1], arr[i]];
    }
    i--;
  }
  return arr;
}
function GetMedian() {
  // write code here
  if (!arr.length) return null;
  var length = arr.length;
  var mid = parseInt(length / 2);
  if (length % 2 === 0) {
    var avg = (arr[mid - 1] + arr[mid]) / 2;
    return avg;
  } else {
    return arr[mid];
  }
}

以上是关于剑指offer - 树的主要内容,如果未能解决你的问题,请参考以下文章

Java 剑指offer(36) 二叉搜索树与双向链表

剑指OFFER 把二叉树打印成多行

剑指 Offer 33. 二叉搜索树的后序遍历序列(java解题)

剑指Offer平衡二叉树(树)

剑指Offer(牛客版)--面试题55:二叉树平衡树

剑指 Offer 36. 二叉搜索树与双向链表