第24题:987. 二叉树的垂序遍历
给你二叉树的根节点 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的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。
二叉树的垂序遍历过程:
- 构建节点与节点位置的遍历结果;
- 对遍历结果按照列索引递增排序;如果列相同则按行索引递增排序;如果列索引和行索引都相同,继续对节点的值递增排序;
- 将排序后的节点按列依次存入数组。
为方便表示,可以将每个节点的值与位置信息保存为一个包含列索引、行索引、节点值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;
}