返回

前端知识库 · 刷题之「填充每个节点的下一个右侧节点指针 II」的深度探索

前端

解剖「填充每个节点的下一个右侧节点指针 II」:前端算法修炼之旅

算法篇

在前端开发的浩瀚知识海洋中,算法和数据结构是两座不可逾越的高峰。而 LeetCode 题库中的「填充每个节点的下一个右侧节点指针 II」题目,恰似一座修炼塔,考验着我们的算法思维和对数据结构的理解。今天,我们就踏上探险之旅,共同剖析这道难题,揭开它的奥秘。

题意解析

题目给定一棵「完美二叉树」,即每一层都被完全填满的二叉树,要求我们:

  1. 修改树,让每个节点指向其右侧的兄弟节点。
  2. 最后返回一个任意节点。

完美二叉树的特点在于,每一层节点数为 2 的 k 次方(k >= 1),且除最底层外,其余各层均被完全填满。而题目中给定的节点列表,则是按照任意顺序排列的,不遵循层次关系。

解法一:广度优先搜索(BFS)

广度优先搜索(BFS)是一种从根节点开始,逐层遍历树或图的算法。对于本题,我们可以使用 BFS 来遍历树,同时维护一个队列,在遍历过程中将当前节点的右侧兄弟节点放入队列中。

算法步骤

  1. 初始化一个队列 queue,并将其根节点入队。
  2. 循环队列 queue,直至其为空:
    • 取出队首节点 node
    • 如果 node 有右兄弟节点,则将其入队。
    • node 的右侧兄弟节点指针指向队列中下一个节点(即队首)。
  3. 返回任意节点。

代码示例(JavaScript)

/**
 * Definition for a Node.
 * function Node(val, left, right, next) {
 *    this.val = val;
 *    this.left = left;
 *    this.right = right;
 *    this.next = next;
 * };
 */
const connect = function (root) {
  if (!root) return null;

  const queue = [root];
  while (queue.length) {
    const size = queue.length;
    for (let i = 0; i < size; i++) {
      const node = queue.shift();
      if (i < size - 1) {
        node.next = queue[0];
      }
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
  }

  return root;
};

解法二:深度优先搜索(DFS)

深度优先搜索(DFS)是一种从根节点开始,深度优先地探索每个分支的算法。对于本题,我们可以使用 DFS 来遍历树,在遍历过程中维护一个 prev 指针,该指针指向当前节点的前一个节点。

算法步骤

  1. 递归函数 dfs 接受三个参数:当前节点 node、当前层的最后一个节点 prev 和当前层是否为奇数 isOdd
  2. 如果 node 为空,则返回。
  3. 如果 isOdd 为真,则将 node 的下一个指针指向 prev。否则,将 node 的下一个指针指向 prev 的右孩子。
  4. 递归调用 dfs 遍历 node 的左孩子和右孩子。

代码示例(JavaScript)

/**
 * Definition for a Node.
 * function Node(val, left, right, next) {
 *    this.val = val;
 *    this.left = left;
 *    this.right = right;
 *    this.next = next;
 * };
 */
const connect = function (root) {
  if (!root) return null;

  const dfs = (node, prev, isOdd) => {
    if (!node) return;

    if (isOdd) {
      node.next = prev;
    } else {
      node.next = prev ? prev.right : null;
    }

    dfs(node.left, node, true);
    dfs(node.right, prev, false);
  };

  dfs(root, null, false);

  return root;
};

解法三:基于层的迭代

我们可以将树视为由多个层组成,每一层包含多个节点。对于每一层,我们可以按从左到右的顺序遍历节点,并更新每个节点的下一个指针。

算法步骤

  1. 使用队列 queue 存储当前层的节点。
  2. 循环 queue,直到其为空:
    • 取出队首节点 node
    • 如果 node 有右兄弟节点,则将其入队。
    • node 的下一个指针指向队首节点。
  3. queue 清空。
  4. 重复步骤 2 和 3,直到遍历完所有层。

代码示例(JavaScript)

/**
 * Definition for a Node.
 * function Node(val, left, right, next) {
 *    this.val = val;
 *    this.left = left;
 *    this.right = right;
 *    this.next = next;
 * };
 */
const connect = function (root) {
  if (!root) return null;

  let queue = [root];
  while (queue.length) {
    const size = queue.length;
    for (let i = 0; i < size; i++) {
      const node = queue.shift();
      if (i < size - 1) {
        node.next = queue[0];
      }
      if (node.left) queue.push(node.left);
      if (node.right) queue.push(node.right);
    }
    queue = [];
  }

  return root;
};

总结

通过剖析「填充每个节点的下一个右侧节点指针 II」题目,我们深入理解了 BFS、DFS 和基于层的迭代这三种解决该问题的经典算法。每种算法都有其独特的优势和劣势,在实际场景中,开发者可以根据具体情况选择最适合的解法。

刷题是提升前端技术水平的有效途径,它不仅能够锻炼我们的算法思维,还能加深我们对数据结构的理解。本文抛砖引玉,期待与各位前端爱好者共同探索算法与数据结构的奥妙,在前端知识的海洋中乘风破浪。

常见问题解答

  1. 这道题目是 LeetCode 的哪种难度?

    • 中等难度。
  2. 为什么需要填充右侧指针?

    • 在某些场景中,我们可能需要快速访问节点的右侧兄弟节点,例如在序列化或反序列化二叉树时。
  3. 这道题目有哪些变种?

    • 「填充每个节点的下一个右侧节点指针」,无需考虑完美二叉树。
    • 「填充每个节点的下一个右侧节点指针 III」,完美二叉树,但父指针指向下一层的节点。
  4. 除了 BFS、DFS 和基于层的迭代,还有没有其他解决方法?

    • 栈迭代、递归等。
  5. 这道题目对面试有哪些帮助?

    • 考核算法思维、数据结构理解能力和编码能力。