二叉树相关算法题

Posted by violetks on April 6, 2021

树或者链表的题大部分是要靠递归来做的。

第一题:翻转二叉树

原二叉树:
    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9  11
镜像二叉树:
    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7   5

分析:针对每个节点都要进行同样的操作,左子树和右子树互换,当到达最后的叶子节点终止。

// function TreeNode (x) {
//   this.val = x;
//   this.left = null;
//   this.right = null;
// }
function Mirror (root) {
  // 定义翻转函数
  function swap (node) {
    if (!node) return;
    // 使用解构赋值进行左右节点交换
    [node.left, node.right] = [node.right, node.left];
    // 递归,对子节点进行相同操作
    swap(node.left);
    swap(node.right);
  }
  return swap(root);
}

知识点:二叉树的几种遍历方式

  • 前序遍历:root left right
  • 中序遍历:left root right
  • 后序遍历:left right root

第二题:从前序与中序遍历序列构造二叉树

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

分析:
1、在前序遍历找到根节点 root。
2、在中序遍历中,根节点左侧的{4,7,2}就是 left 节点,右侧{5,3,8,6}是 right 节点。
3、前序遍历的{2,4,7}按照规则,2 是 root…以此类推。

// function TreeNode (x) {
//   this.val = x;
//   this.left = null;
//   this.right = null;
// }
// 前序遍历:preorder = [1,2,4,7,3,5,6,8];
// 中序遍历:inorder = [4,7,2,1,5,3,8,6];
function reConstructBinaryTree (preorder, inorder) {
  if (!preorder.length || !inorder.length) return null;  // 递归终止条件
  let rootVal = preorder[0];        // 1.从前序遍历找到根节点
  let root = new TreeNode(rootVal); // 构造根节点
  let i = inorder.findIndex(e => e === rootVal);  // 2.在中序遍历找到根节点的索引
  // let i = inorder.indexOf(rootVal);
  // 3.划分左子树和右子树
  // 中序遍历:root 的左边是左子树,用 inorder.slice(0, i) 截取,右子树用 inorder.slice(i+1) 截取。
  // 前序遍历:root 后面 i 项都是左子树,用 preorder.slice(1, i+1) 截取,从 i+1 项都是右子树,用 preorder.slice(i+1) 截取。
  // 截取到的子数组又分别符合前序遍历和中序遍历规律,所以递归
  root.left = reConstructBinaryTree(preorder.slice(1, i+1), inorder.slice(0, i));
  root.right = reConstructBinaryTree(preorder.slice(i+1), inorder.slice(i+1));
  return root;
}

第三题:从中序与后序遍历序列构造二叉树

  • 中序遍历 inorder = [9,3,15,20,7]
  • 后序遍历 postorder = [9,15,7,20,3]
// 中序遍历 inorder = [9,3,15,20,7]
// 后序遍历 postorder = [9,15,7,20,3]
function buildTree (inorder, postorder) {
  if (!inorder.length || !postorder.length) return null;
  let rootVal = postorder[postorder.length-1];  // 后序最后一项
  // 创建根节点
  let root = new TreeNode(rootVal);
  // 查找根节点在中序遍历索引
  let i = inorder.indexOf(rootVal);   // i=1
  // 划分左子树与右子树
  root.left = buildTree(inorder.slice(0, i), postorder.slice(0, i));
  root.right = buildTree(inorder.slice(i+1), postorder.slice(i, postorder.length-1));
  return root;
}

第四题:从前序与后序遍历序列构造二叉树

  • 前序遍历 preorder = [1,2,4,5,3,6,7] root left right
  • 后序遍历 postorder = [4,5,2,6,7,3,1] left right root
    	    1
    	   /  \
    	  2    3
    	 / \  / \
    	4   5 6  7

分析:要怎么划分左子树和右子树?
根据前序遍历的第一个左节点来划分,就是题中的 2

function constructFromPrePost = function (pre, post) {
  if (!pre.length || !post.length) return null;
  let rootVal = pre[0];
  let root = new TreeNode(rootVal);
  if (pre.length < 1) return null;
  let i = post.indexOf(pre[1]);   // 前序遍历中第一个左节点索引
  root.left = constructFromPrePost(pre.slice(1, i+2), post.slice(0, i+1));
  root.right = constructFromPrePost(pre.slice(i+2), post.slice(i+1, post.length-1));
  return root;
}

第五题:二叉树的最大深度和最小深度

最大深度

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

function maxDepth (root) {
  if (root === null) return 0;
  let lefth = maxDepth(root.left);
  let righth = maxDepth(root.right);
  return Math.max(lefth, righth) + 1;
}

最小深度

function minDepth (root) {
  if (root === null) return 0;
  let leftm = minDepth(root.left);
  let rightm = minDepth(root.right);
  if (!root.left || !root.right) return Math.max(leftm, right) + 1;
  return Math.min(leftm, right) + 1;
}