前端算法面试必刷题系列[40]

229 阅读5分钟

这个系列没啥花头,就是纯 leetcode 题目拆解分析,不求用骚气的一行或者小众取巧解法,而是用清晰的代码和足够简单的思路帮你理清题意。让你在面试中再也不怕算法笔试。

71. 二叉树的中序遍历 (binary-tree-inorder-traversal)

标签

  • 二叉树遍历
  • 简单

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

给定一个二叉树的根节点 root ,返回它的 中序 遍历。

输入:root = [1,null,2,3]
输出:[1,3,2]

基本思路

大哥这篇写的非常清楚详细了,不赘述。 二叉树的 先序/中序/后序 遍历

写法实现

  • 递归实现
const midOrderTraversalRecursion = (root) => {
  let resList = []
  const midOrderTraversal = (node) => {
    if(node !== null) {
      // 先遍历左子树
      midOrderTraversal(node.left)           // ---------> (左)
      // 然后访问根节点
      resList.push(node.val)                 // ---------> (根)
      // 再遍历右子树
      midOrderTraversal(node.right)          // ---------> (右)
    }
  }
  midOrderTraversal(root)
  return resList
}
  • 迭代实现
const midOrderTraversalIteration = (root) => {
  const resList = []
  let stack = []
  let curNode = null

  if (root !== null) {
    curNode = root
  }

  // 当栈不为空 或者 当前节点不为空时进行循环
  while (stack.length > 0 || curNode !== null) {
    // 中序遍历,是先访问左子树 => 再根 => 再右子树
    while (curNode !== null) {
      // 我们现在当前的是根,得先入栈,把当前节点置为它的左子树
      stack.push(curNode)
      curNode = curNode.left
    }
    if (stack.length > 0) {
      // 从栈中取东西了,把最上面先推出来
      curNode = stack.pop()
      // 进行访问, 输入数据到结果序列
      resList.push(curNode.val)
      // 把当前节点置为它的右子树
      curNode = curNode.right
    }
  }
}

这个看的可能一脸懵逼,简单来说下面几个步骤,尝试理解

  1. 遇到根节点,先入栈,如果它有左节点,那么继续入栈左节点直到当前入栈的节点没有左子树了,那么可以理解为,当前为最左边的节点
  2. 出栈,栈顶是刚入栈的最左节点 并 打印 最左边的节点
  3. 如果这个最左节点有右子树,那么当前应该访问它的右子树了,因为这个节点没有左儿子,现在它自己被打印了当成根看,下面就是右子树的访问了 左 根 右
  4. 所以,当前节点变成了右子树,本次循环结束
  5. 把现在的 右子树 当成第一次进来的根 来进行下一轮循环
  6. 再从第一步开始 入栈,看有没有左子树 继续找这个节点的最左节点 入栈最左节点。。。

72. 不同的二叉搜索树 II (unique-binary-search-trees-ii)

标签

  • DFS
  • BST (binary-search-trees)
  • 中等

题目

leetcode 传送门

这里不贴题了,leetcode打开就行,题目大意:

给定一个整数 n,生成所有由 1 ... n 为节点所组成的 二叉搜索树

输入:3
输出:
[
  [1,null,3,2],
  [3,2,null,1],
  [3,1,null,null,2],
  [2,1,3],
  [1,null,2,null,3]
]
解释:
以上的输出对应以下 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3

相关知识

二叉搜索树BST(binary-search-trees)关键的性质是

  1. 根节点的值大于左子树所有节点的值
  2. 根节点的值小于右子树所有节点的值
  3. 左子树右子树同样为二叉搜索树

还有就是分治法

分治法:“分而治之” —— 就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并

基本思路

我们要生成所有可行的二叉搜索树的时候,假设当前序列长度为n,我们把每个元素,从左到右都做为根,作为基准,来遍历,值为 i,那么根据二叉搜索树的性质我们可以知道左子树的节点值的集合范围为 [1,i − 1],右子树的节点值的集合范围为 [i + 1, n],那么我们就可以左边右边继续分治,因为左边 [1,i − 1] 其实就是总问题的一个子集,最后合并解就行了。

基本步骤

  1. dfs (left, right) 这个函数的返回值就表示 在[left, right]范围所有子树情况。
  2. 从左到右每个数遍历,对于任意一个i,左区间产生的所有子树情况,其实就是 dfs(left, i - 1),是总问题的一个子问题。
  3. 从左到右每个数遍历,对于任意一个i,其左区间产生的所有子树情况为leftTrees,右区间产生的所有子树情况为rightTrees,那么所有情况数就是这左右情况数的乘积
  4. 生成各种情况的 BST 推进 res

写法实现

let generateTrees = function(n) {
  const dfs = (left, right) => {
    // 已经到底了
    if (left > right) {
      return [null]
    }
    let res = []

    for (let i = left; i <= right; i++) {
      // i 是当前作为根的 index,下面进行分治
      let leftTrees = dfs(left, i - 1)
      let rightTrees = dfs(i + 1, right)
      // 下面要所有种类,其实就是集合的笛卡尔乘积
      for (let leftTree of leftTrees) {
        for (let rightTree of rightTrees) {
          let root = new TreeNode(i);
          root.left = leftTree
          root.right = rightTree
          res.push(root)
        }
      }
    }

    return res;
  }
  return dfs(1, n);
}

console.log(generateTrees(3))

另外向大家着重推荐下这位大哥的文章,非常深入浅出,对前端进阶的同学非常有作用,墙裂推荐!!!核心概念和算法拆解系列

今天就到这儿,想跟我一起刷题的小伙伴可以加我微信哦 搜索我的微信号infinity_9368,可以聊天说地 加我暗号 "天王盖地虎" 下一句的英文,验证消息请发给我 presious tower shock the rever monster,我看到就通过,暗号对不上不加哈,加了之后我会尽我所能帮你,但是注意提问方式,建议先看这篇文章:提问的智慧

参考