二叉树的垂序遍历

138 阅读5分钟

第24题:987. 二叉树的垂序遍历

leetcode.cn/problems/ve…

给你二叉树的根节点 root ,请你设计算法计算二叉树的 垂序遍历 序列。

对位于 (row, col) 的每个节点而言,其左右子节点分别位于 (row + 1, col - 1) 和 (row + 1, col + 1) 。树的根节点位于 (0, 0) 。

二叉树的 垂序遍历 从最左边的列开始直到最右边的列结束,按列索引每一列上的所有节点,形成一个按出现位置从上到下排序的有序列表。如果同行同列上有多个节点,则按节点的值从小到大进行排序。

返回二叉树的 垂序遍历 序列。

示例 1: 图片

输入:root = [3,9,20,null,null,15,7]

输出:[[9],[3,15],[20],[7]]

解释:

列 -1 :只有节点 9 在此列中。

列  0 :只有节点 3 和 15 在此列中,按从上到下顺序。

列  1 :只有节点 20 在此列中。

列  2 :只有节点 7 在此列中。

示例 2: 图片

输入:root = [1,2,3,4,5,6,7]

输出:[[4],[2],[1,5,6],[3],[7]]

解释:

列 -2 :只有节点 4 在此列中。

列 -1 :只有节点 2 在此列中。

列  0 :节点 1 、5 和 6 都在此列中。

            1 在上面,所以它出现在前面。

            5 和 6 位置都是 (2, 0) ,所以按值从小到大排序,5 在 6 的前面。

列  1 :只有节点 3 在此列中。

列  2 :只有节点 7 在此列中。

示例 3: 图片

输入:root = [1,2,3,4,6,5,7]

输出:[[4],[2],[1,5,6],[3],[7]]

解释:

这个示例实际上与示例 2 完全相同,只是节点 5 和 6 在树中的位置发生了交换。

因为 5 和 6 的位置仍然相同,所以答案保持不变,仍然按值从小到大排序。

提示:

树中节点数目总数在范围 [1, 1000] 内

0 <= Node.val <= 1000

/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var verticalTraversal = function(root) {
  const nodes = [];
  // 构建
  dfs(root, 0, 0, nodes);
  // 排序
  nodes.sort((tuple1, tuple2) => {
    // 按列索引
    if (tuple1[0] !== tuple2[0]) return tuple1[0] - tuple2[0];
    // 按行索引
    else if (tuple1[1] !== tuple2[1]) return tuple1[1] - tuple2[1];
    // 按节点值
    else return tuple1[2] - tuple2[2];
  });
  // 存放输出结果
  const result = [];
  // 当前列索引
  let tmpCol = null;
  for (const tuple of nodes) {
    let col = tuple[0], value = tuple[2];
    // 如果当前节点与上一节点不在同一列
    // 记录当前的列索引,并将其放入新的元数组中
    if (col !== tmpCol) {
      tmpCol = col;
      result.push([value]);
    }
    // 否则,将其放入当前的元数组中
    else {
      result[result.length - 1].push(value);
    }
  }

    return result;
};
// 将列索引,行索引,节点值组成元数组存放
function dfs(root, row, col, nodes) {
    if (!root) return;
    nodes.push([col, row, root.val]);
    dfs(root.left, row + 1, col - 1, nodes);
    dfs(root.right, row + 1, col + 1, nodes);
}

题目解析

根据题目中的描述,可以得到二叉树所有节点的位置信息,直接使用递归遍历即可以实现:

/**
 * 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)
 * }
 */

// 定义一个变量存储遍历的结果
const result = [];
function recursive(root, row, col, result) {
  if (!root) return;
  // 创建节点值与位置信息的对象
  let node = {
    value: root.val,
    position: [row, col]
  }
  // 将节点信息放入结果集
  result.push(node);
  // 递归左右子树
  recursive(root.left, row + 1, col - 1, result);
  recursive(root.right, row + 1, col + 1, result);
}

该递归基于DFS(Depth-First Search,深度优先搜索)。DFS会尽可能深地搜索树的分支,当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。

二叉树的垂序遍历过程:

  1. 构建节点与节点位置的遍历结果;
  2. 对遍历结果按照列索引递增排序;如果列相同则按行索引递增排序;如果列索引和行索引都相同,继续对节点的值递增排序;
  3. 将排序后的节点按列依次存入数组。

为方便表示,可以将每个节点的值与位置信息保存为一个包含列索引、行索引、节点值3个元素的一维数组。在进行第2步排序操作时,只需要依次对每个保存节点值与节点位置的一维数组的第0个元素、第1个元素、第2个元素进行排序。

nodes.sort((tuple1, tuple2) => {
  // 按列索引
  if (tuple1[0] !== tuple2[0]) return tuple1[0] - tuple2[0];
  // 按行索引
  else if (tuple1[1] !== tuple2[1]) return tuple1[1] - tuple2[1];
  // 按节点值
  else return tuple1[2] - tuple2[2];
});
// 存放输出结果
const result = [];
// 当前列索引
let tmpCol = null;
for (const tuple of nodes) {
  let col = tuple[0], value = tuple[2];
  // 如果当前节点与上一节点不在同一列
  // 记录当前的列索引,并将其放入新的元数组中
  if (col !== tmpCol) {
    tmpCol = col;
    result.push([value]);
  }
  // 否则,将其放入当前的元数组中
  else {
    result[result.length - 1].push(value);
  }
}

附. 实现一维数组转二叉树代码

function buildBinaryTree(nums) {
  let root = null;
  if (!nums || nums.length === 0) return root;

  // 将数组的第一个元素作为二叉树的根节点
  root = new TreeNode(nums.shift());
  // 定义一个数组存节点信息
  const nodes = [root];
  // 只要数组非空,执行循环
  while(nums.length > 0) {
    // 获取当前节点
    let node = nodes.shift();
    // 数组为空,跳出循环
    if (nums.length === 0) break;
    // 填充非空左子树节点
    let lv = nums.shift();
    if (lv !== null) {
      node.left = new TreeNode(lv)
      nodes.push(node.left);
    }
    
    // 数组为空,跳出循环
    if (nums.length === 0) break;
    // 填充非空右子树结点
    let rv = nums.shift();
    if (rv !== null) {
      node.right = new TreeNode(rv)
      nodes.push(node.right)
    }
  }

  return root;
}