算法题解合集(JS)

85 阅读8分钟

二叉树类

二叉树的最大深度

    /**
     * 给定一个二叉树,找出其最大深度。、
     * 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
     * 说明: 叶子节点是指没有子节点的节点。

     * 示例:
     * 给定二叉树 [3,9,20,null,null,15,7],回它的最大深度 3 。
     */

    /**
     * Definition for a binary tree node.
     * function TreeNode(val, left, right) {
     *     this.val = (val===undefined ? 0 : val)
     *     this.left = (left===undefined ? null : left)
     *     this.right = (right===undefined ? null : right)
     * }
     */

    /**
     * 方法一:动态规划思路
     * 执行用时:68 ms, 在所有 JavaScript 提交中击败了61.36%的用户
     * 内存消耗:43.6 MB, 在所有 JavaScript 提交中击败了76.52%的用户
     * @param {TreeNode} root
     * @return {number}
     */
    const maxDepth = function (root) {
      // 求二叉树最大深度,即需要得知左子树和右子树的最大深度,当前深度+1即可? 后序遍历
      if (root === null) {
        return 0;
      }
      let maxLeft = maxDepth(root.left);
      let maxRight = maxDepth(root.right);

      return Math.max(maxLeft, maxRight) + 1;
    };

    console.log(maxDepth([3, 9, 20, null, null, 15, 7]));

    function TreeNode(val, left, right) {
      this.val = val === undefined ? 0 : val;
      this.left = left === undefined ? null : left;
      this.right = right === undefined ? null : right;
    }

    /**
     * 方法二:遍历思路-回溯算法思路
     * 执行用时:68 ms, 在所有 JavaScript 提交中击败了61.36%的用户
     * 内存消耗:43.9 MB, 在所有 JavaScript 提交中击败了32.31%的用户
     * @param {TreeNode} root
     * @return {number}
     */
    let res = 0;
    let depth = 0;
    const maxDepthBack1 = function (root) {
      res = 0;
      depth = 0;

      traverse(root);
      return res;
    };

    const traverse = function (root) {
      if (root === null) {
        return;
      }

      depth++;
      // 如果到了叶子节点则更新最大深度
      if (root.left === null && root.right === null) {
        res = Math.max(res, depth);
      }
      traverse(root.left);
      traverse(root.right);
      depth--;
    };

    /**
     * 方法三:遍历思路-回溯算法思路
     * 执行用时:64 ms, 在所有 JavaScript 提交中击败了80.08%的用户
     * 内存消耗:44.2 MB, 在所有 JavaScript 提交中击败了10.96%的用户
     * @param {TreeNode} root
     * @return {number}
     */
    const maxDepthBack2 = function (root) {
      let res = 0;
      let depth = 0;

      const traverse = function (root) {
        if (root === null) {
          return;
        }

        depth++;
        // 如果到了叶子节点则更新最大深度
        if (root.left === null && root.right === null) {
          res = Math.max(res, depth);
        }
        traverse(root.left);
        traverse(root.right);
        depth--;
      };

      traverse(root);
      return res;
    };

类似问题:二叉树的深度

二叉树的前序遍历

    /**
     * 二叉树的前序遍历
     * 给你二叉树的根节点 root ,返回它节点值的 前序 遍历。

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

     * 示例2:
     * 输入:root = []
     * 输出:[]

     * 示例3:
     * 输入:root = [1]
     * 输出:[1]

     * 示例4:
     * 输入:root = [1,2]
     * 输出:[1,2]

     * 示例5:
     * 输入:root = [1,null,2]
     * 输出:[1,2]
     */

    /**
     * Definition for a binary tree node.
     * function TreeNode(val, left, right) {
     *     this.val = (val===undefined ? 0 : val)
     *     this.left = (left===undefined ? null : left)
     *     this.right = (right===undefined ? null : right)
     * }
     */

    /**
     * 方法一:回溯算法思路  ***** 更优解
     * 执行用时:48 ms, 在所有 JavaScript 提交中击败了99.01%的用户
     * 内存消耗:40.9 MB, 在所有 JavaScript 提交中击败了92.72%的用户
     * @param {TreeNode} root
     * @return {number[]}
     */
    var preorderTraversal = function (root) {
      const arr = [];
      // 递归前序遍历

      const traverse = function (root) {
        if (root === null) return;

        arr.push(root.val);
        traverse(root.left);
        traverse(root.right);
      };

      traverse(root);

      return arr;
    };

    /**
     * 方法二:动态规划思路 - 前序遍历
     * 执行用时:52 ms, 在所有 JavaScript 提交中击败了95.92%的用户
     * 内存消耗:41.7 MB, 在所有 JavaScript 提交中击败了5.07%的用户
     * @param {TreeNode} root
     * @return {number[]}
     */
    var preorderTraversal = function (root) {
      // 递归前序遍历
      const res = [];
      if (root === null) return [];

      res.push(root.val);
      res.push(...preorderTraversal(root.left));
      res.push(...preorderTraversal(root.right));

      return res;
    };

二叉树的直径

    /**
     * 给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

     * 示例 :
     * 给定二叉树

           1
          / \
         2   3
        / \     
       4   5    
     * 返回 3, 它的长度是路径 [4,2,1,3] 或者 [5,2,1,3]。
     *  注意:两结点之间的路径长度是以它们之间边的数目表示。
     */

    /**
     * Definition for a binary tree node.
     * function TreeNode(val, left, right) {
     *     this.val = (val===undefined ? 0 : val)
     *     this.left = (left===undefined ? null : left)
     *     this.right = (right===undefined ? null : right)
     * }
     */

    /**
     * 方案一:递归方法,后序遍历。每次递归当前根节点的左子树和右子树的最大深度
     * 遇到子树问题,首先想到的是给函数设置返回值,然后在后序位置做文章。
     * @param {TreeNode} root
     * @return {number}
     * 执行用时:60 ms, 在所有 JavaScript 提交中击败了94.50%的用户
     * 内存消耗:44.4 MB, 在所有 JavaScript 提交中击败了50.71%的用户
     */
    var diameterOfBinaryTree = function (root) {
      let res = 0;

      var traverse = function (root) {
        if (root === null) return 0;

        let leftMaxLen = traverse(root.left);
        let rightMaxLen = traverse(root.right);
        // 后续遍历计算最大直径,即离开当前节点时再计算最大深度
        res = Math.max(res, leftMaxLen + rightMaxLen);
        return 1 + Math.max(leftMaxLen, rightMaxLen); // 返回当前节点最长路径
      };

      traverse(root);
      return res;
    };

二叉树展开为链表

    /**
     * 给你二叉树的根结点 root ,请你将它展开为一个单链表:
     * 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 
     * 展开后的单链表应该与二叉树 先序遍历 顺序相同。

     * 示例 1:
     * 输入:root = [1,2,5,3,4,null,6]
     * 输出:[1,null,2,null,3,null,4,null,5,null,6]

     * 示例 2:
     * 输入:root = []
     * 输出:[]
    
     * 示例 3:
     * 输入:root = [0]
     * 输出:[0]
     */

    /**
     * Definition for a binary tree node.
     * function TreeNode(val, left, right) {
     *     this.val = (val===undefined ? 0 : val)
     *     this.left = (left===undefined ? null : left)
     *     this.right = (right===undefined ? null : right)
     * }
     */

    /**
     * 思路一:递归思路,将左子树及右子树都改为链表形式,最后将右子树拼到左子树之后,用左子树覆盖到root右侧
     * @param {TreeNode} root
     * @return {void} Do not return anything, modify root in-place instead.
     * 执行用时:52 ms, 在所有 JavaScript 提交中击败了99.79%的用户
     * 内存消耗:42.9 MB, 在所有 JavaScript 提交中击败了93.51%的用户
     */
    var flatten = function (root) {
      if (root === null) return;

      flatten(root.left);
      flatten(root.right);

      const left = root.left;
      const right = root.right;

      // 将右子树拼到左子树之后,如果左子树是null,则无需处理
      if (left !== null) {
        let endLeftNode = left;
        while (endLeftNode.right !== null) {
          endLeftNode = endLeftNode.right;
        }
        endLeftNode.right = right;

        // 将整个左子树加到root右侧
        root.left = null;
        root.right = left;
      } else {
        root.left = null;
      }
    };

    /**
     * 思路二:递归思路,将左子树及右子树都改为链表形式,最后将左子树拼到根节点之后,再找到叶子右节点,将右子树拼接其后
     * @param {TreeNode} root
     * @return {void} Do not return anything, modify root in-place instead.
     * 性能略低于上面
     */
    var flatten2 = function (root) {
      if (root === null) return;

      flatten(root.left);
      flatten(root.right);

      const left = root.left;
      const right = root.right;

      // 将整个左子树加到root右侧
      root.left = null;
      root.right = left;

      // 将右子树拼接到root最后右节点上
      let endRight = root;
      while (endRight.right !== null) {
        endRight = endRight.right;
      }
      endRight.right = right;
    };

填充每个节点的下一个右侧节点指针

    /**
     * 填充每个节点的下一个右侧节点指针
     * 给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
     * struct Node {
     *   int val;
     *   Node *left;
     *   Node *right;
     *   Node *next;
     * }
     * 填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
     * 初始状态下,所有 next 指针都被设置为 NULL。

     * 示例 1:
     * 输入:root = [1,2,3,4,5,6,7]
     * 输出:[1,#,2,3,#,4,5,6,7,#]
     * 解释:给定二叉树如图 A 所示,你的函数应该填充它的每个 next 指针,以指向其下一个右侧节点,如图 B 所示。序列化的输出按层序遍历排列,同一层节点由 next 指针连接,'#' 标志着每一层的结束。

     * 示例 2:
     * 输入:root = []
     * 输出:[]
     */

    /**
     * // Definition for a Node.
     * function Node(val, left, right, next) {
     *    this.val = val === undefined ? null : val;
     *    this.left = left === undefined ? null : left;
     *    this.right = right === undefined ? null : right;
     *    this.next = next === undefined ? null : next;
     * };
     */

    /**
     * 方案一:通过遍历思路,当前节点的右节点的next指向当前节点next的兄弟节点的左节点即可 , 最优方案
     * @param {Node} root
     * @return {Node}
     * 执行用时:64 ms, 在所有 JavaScript 提交中击败了98.86%的用户
     * 内存消耗:47 MB, 在所有 JavaScript 提交中击败了88.25%的用户
     */
    var connect = function (root) {
      if (root === null || root.left === null) return root;

      function traverse(root) {
        // 如果当前节点为空或为叶子节点,则直接返回
        if (root === null || root.left === null) return;

        // left节点不空,则完美二叉树的right节点也一定存在。 遍历后更改next指向;此处一定要前序遍历,要先赋值next节点。
        root.left.next = root.right;
        if (root.next !== null) {
          root.right.next = root.next.left;
        }

        // 遍历左右子节点
        traverse(root.left);
        traverse(root.right);
      }

      traverse(root);
      return root;
    };

    /**
     * 方案二:通过递归思路,当前节点的右节点的next指向当前节点next的兄弟节点的左节点即可
     * @param {Node} root
     * @return {Node}
     * 执行用时:112 ms, 在所有 JavaScript 提交中击败了5.46%的用户
     * 内存消耗:47.6 MB, 在所有 JavaScript 提交中击败了28.42%的用户
     */
    var connect1 = function (root) {
      if (root == null) return null;

      function traverse(leftNode, rightNode) {
        // 如果当前节点为叶子节点
        if (leftNode === null || rightNode === null) return;

        // 遍历左右子节点
        leftNode.next = rightNode;
        traverse(leftNode.left, leftNode.right);
        traverse(rightNode.left, rightNode.right);

        // 拼接交界处
        traverse(leftNode.right, rightNode.left);
      }

      traverse(root.left, root.right);
      return root;
    };

翻转二叉树

    /**
     * 翻转二叉树
     * 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。

     * 示例 1:
     * 输入:root = [4,2,7,1,3,6,9]
     * 输出:[4,7,2,9,6,3,1]

     * 示例 2:
     * 输入:root = [2,1,3]
     * 输出:[2,3,1]

     * 示例 3:
     * 输入:root = []
     * 输出:[]
     */

    /**
     * Definition for a binary tree node.
     * function TreeNode(val, left, right) {
     *     this.val = (val===undefined ? 0 : val)
     *     this.left = (left===undefined ? null : left)
     *     this.right = (right===undefined ? null : right)
     * }
     */

    /**
     * 翻转二叉树
     * 思路一:递归思路,将左右子树互换位置,前序遍历
     * @param {TreeNode} root
     * @return {TreeNode}
     * https://leetcode.cn/problems/invert-binary-tree/
     * 执行用时:76 ms, 在所有 JavaScript 提交中击败了5.45%的用户
     * 内存消耗:41.5 MB, 在所有 JavaScript 提交中击败了33.66%的用户
     * https://leetcode.cn/problems/er-cha-shu-de-jing-xiang-lcof/submissions/
     * 执行用时:56 ms, 在所有 JavaScript 提交中击败了88.18%的用户
     * 内存消耗:41.3 MB, 在所有 JavaScript 提交中击败了57.62%的用户
     */
    var invertTree = function (root) {
      if (root === null) return root;

      function traverse(root) {
        if (root === null) return;

        // 假设此二叉树是完美二叉树
        const leftNode = root.left;
        const rightNode = root.right;
        root.left = null;
        root.right = null;
        if (leftNode !== null) {
          root.right = leftNode;
        }
        if (rightNode !== null) {
          root.left = rightNode;
        }

        traverse(root.left);
        traverse(root.right);
      }

      traverse(root);
      return root;
    };

类似问题:二叉树的镜像

规划

后续会陆续加入其他类型算法题解,当然也欢迎大家提供更好的idea。

相关文档及链接

labuladong.github.io/algo/2/

leetcode.cn/