【算法挑战】二叉树的垂序遍历(含解析、源码)

987. 二叉树的垂序遍历

https://leetcode-cn.com/problems/vertical-order-traversal-of-a-binary-tree/

题目描述

给定二叉树,按垂序遍历返回其结点值。

对位于 (X, Y) 的每个结点而言,其左右子结点分别位于 (X-1, Y-1) 和 (X+1, Y-1)。

把一条垂线从 X = -infinity 移动到 X = +infinity ,每当该垂线与结点接触时,我们按从上到下的顺序报告结点的值( Y 坐标递减)。

如果两个结点位置相同,则首先报告的结点值较小。

按 X 坐标顺序返回非空报告的列表。每个报告都有一个结点值列表。

 

示例 1:



输入:[3,9,20,null,null,15,7]
输出:[[9],[3,15],[20],[7]]
解释:
在不丧失其普遍性的情况下,我们可以假设根结点位于 (0, 0):
然后,值为 9 的结点出现在 (-1, -1);
值为 3 和 15 的两个结点分别出现在 (0, 0) 和 (0, -2);
值为 20 的结点出现在 (1, -1);
值为 7 的结点出现在 (2, -2)。
示例 2:



输入:[1,2,3,4,5,6,7]
输出:[[4],[2],[1,5,6],[3],[7]]
解释:
根据给定的方案,值为 5 和 6 的两个结点出现在同一位置。
然而,在报告 "[1,5,6]" 中,结点值 5 排在前面,因为 5 小于 6。
 

提示:

树的结点数介于 1 和 1000 之间。
每个结点值介于 0 和 1000 之间。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/vertical-order-traversal-of-a-binary-tree
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方法 1:DFS 记录坐标+排序

思路

其实理解了题意之后,就是一个简单的 DFS+排序 而已。

我们把二叉树放到坐标网格上看看。以根节点为原点,往左的节点 x--,往右的节点 x++,往下的节点 y--

  • 把节点坐标和值按 x 坐标分组,然后给这些分组按升序排序。
  • 在分组内,给节点按 y 坐标降序排序,如果 y 坐标相同,再按节点值升序排。

在这里插入图片描述

复杂度分析

  • 时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN),N 为二叉树的节点数。遍历二叉树的时间是 O ( N ) O(N) O(N),排序的的时间就认为是 O ( N l o g N ) O(NlogN) O(NlogN) 吧。
  • 空间复杂度: O ( N ) O(N) O(N),N 为二叉树的节点数。用来存储节点坐标信息的 pos 空间是 N,递归栈的空间是 O ( h ) O(h) O(h),h 为二叉树高度。

代码

JavaScript Code

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var verticalTraversal = function (root) {
    if (!root) return [];

    // 坐标集合以 x 坐标分组
    const pos = {};
    // dfs 遍历节点并记录每个节点的坐标
    dfs(root, 0, 0);

    // 得到所有节点坐标后,先按 x 坐标升序排序
    let sorted = Object.keys(pos)
        .sort((a, b) => +a - +b)
        .map(key => pos[key]);

    // 再给 x 坐标相同的每组节点坐标分别排序
    sorted = sorted.map(g => {
        g.sort((a, b) => {
            // y 坐标相同的,按节点值升序排
            if (a[0] === b[0]) return a[1] - b[1];
            // 否则,按 y 坐标降序排
            else return b[0] - a[0];
        });
        // 把 y 坐标去掉,返回节点值
        return g.map(el => el[1]);
    });

    return sorted;

    // *********************************
    function dfs(root, x, y) {
        if (!root) return;

        x in pos || (pos[x] = []);
        // 保存坐标数据,格式是: [y, val]
        pos[x].push([y, root.val]);

        dfs(root.left, x - 1, y - 1);
        dfs(root.right, x + 1, y - 1);
    }
};

方法 2:BFS 记录坐标+排序

思路

跟方法 1 的区别只在于遍历方式。

复杂度分析

  • 时间复杂度: O ( N l o g N ) O(NlogN) O(NlogN),N 为二叉树的节点数。遍历二叉树的时间是 O ( N ) O(N) O(N),排序的的时间就认为是 O ( N l o g N ) O(NlogN) O(NlogN) 吧。
  • 空间复杂度: O ( N ) O(N) O(N),N 为二叉树的节点数。用来存储节点坐标信息的 pos 空间是 N,队列的空间是 O ( q ) O(q) O(q),最坏情况下 q 与 N 同阶。

代码

JavaScript Code

/**
 * Definition for a binary tree node.
 * function TreeNode(val) {
 *     this.val = val;
 *     this.left = this.right = null;
 * }
 */
/**
 * @param {TreeNode} root
 * @return {number[][]}
 */
var verticalTraversal = function (root) {
    if (!root) return [];

    // 坐标集合以 x 坐标分组
    const pos = bfs(root);

    // 得到所有节点坐标后,先按 x 坐标升序排序
    let sorted = Object.keys(pos)
        .sort((a, b) => +a - +b)
        .map(key => pos[key]);

    // 再给 x 坐标相同的每组节点坐标分别排序
    sorted = sorted.map(g => {
        g.sort((a, b) => {
            // y 坐标相同的,按节点值升序排
            if (a[0] === b[0]) return a[1] - b[1];
            // 否则,按 y 坐标降序排
            else return b[0] - a[0];
        });
        // 把 y 坐标去掉,返回节点值
        return g.map(el => el[1]);
    });

    return sorted;

    // *********************************
    function bfs(root) {
        // 队列中数据格式是: [x, y, val]
        const queue = [[0, 0, root]];
        const pos = {};
        while (queue.length) {
            let size = queue.length;

            while (size--) {
                const [x, y, node] = queue.shift();
                x in pos || (pos[x] = []);
                // 保存坐标数据到 pos,格式是: [y, val]
                pos[x].push([y, node.val]);
                node.left && queue.push([x - 1, y - 1, node.left]);
                node.right && queue.push([x + 1, y - 1, node.right]);
            }
        }
        return pos;
    }
};

总结

以上就是本文所有内容了,希望能对你有所帮助,能够解决二叉树的垂序遍历问题。

如果你喜欢本文,也请务必点赞、收藏、评论、转发,这会对我有非常大的帮助。请我喝杯冰可乐也是极好的!

已完结,欢迎持续关注。下次见~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sanbaofengs

请我喝一杯冰可乐吧~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值