第五章 - 高频考题(中等)
1906. 查询差绝对值的最小值
0987. 二叉树的垂序遍历

题目地址(987. 二叉树的垂序遍历)

题目描述

1
给定二叉树,按垂序遍历返回其结点值。
2
3
对位于 (X, Y) 的每个结点而言,其左右子结点分别位于 (X-1, Y-1) 和 (X+1, Y-1)。
4
5
把一条垂线从 X = -infinity 移动到 X = +infinity ,每当该垂线与结点接触时,我们按从上到下的顺序报告结点的值(Y 坐标递减)。
6
7
如果两个结点位置相同,则首先报告的结点值较小。
8
9
按 X 坐标顺序返回非空报告的列表。每个报告都有一个结点值列表。
10
11
示例 1:
12
13
输入:[3,9,20,null,null,15,7]
14
输出:[[9],[3,15],[20],[7]]
15
解释:
16
在不丧失其普遍性的情况下,我们可以假设根结点位于 (0, 0):
17
然后,值为 9 的结点出现在 (-1, -1);
18
值为 3 和 15 的两个结点分别出现在 (0, 0) 和 (0, -2);
19
值为 20 的结点出现在 (1, -1);
20
值为 7 的结点出现在 (2, -2)。
21
22
示例 2:
23
24
输入:[1,2,3,4,5,6,7]
25
输出:[[4],[2],[1,5,6],[3],[7]]
26
解释:
27
根据给定的方案,值为 5 和 6 的两个结点出现在同一位置。
28
然而,在报告 "[1,5,6]" 中,结点值 5 排在前面,因为 5 小于 6。
29
30
31
提示:
32
33
树的结点数介于 1 和 1000 之间。
34
每个结点值介于 0 和 1000 之间。
Copied!

前置知识

  • DFS
  • 排序

思路

经过前面几天的学习,希望大家对 DFS 和 BFS 已经有了一定的了解了。
我们先来简化一下问题。假如题目没有从上到下的顺序报告结点的值(Y 坐标递减),甚至也没有如果两个结点位置相同,则首先报告的结点值较小。 的限制。是不是就比较简单了?
Could not load image
如上图,我们只需要进行一次搜索,不妨使用 DFS(没有特殊理由,我一般都是 DFS),将节点存储到一个哈希表中,其中 key 为节点的 x 值,value 为横坐标为 x 的节点值列表(不妨用数组表示)。形如:
1
{
2
1: [1,3,4]
3
-1: [5]
4
}
Copied!
数据是瞎编的,不和题目例子有关联
经过上面的处理, 这个时候只需要对哈希表中的数据进行一次排序输出即可。
ok,如果这个你懂了,我们尝试加上面的两个限制加上去。
  1. 1.
    从上到下的顺序报告结点的值(Y 坐标递减)
  2. 2.
    如果两个结点位置相同,则首先报告的结点值较小。
关于第一个限制。其实我们可以再哈希表中再额外增加一层来解决。形如:
1
{
2
1: {
3
-2,[1,3,4]
4
-3,[5]
5
6
},
7
-1: {
8
-3: [6]
9
}
10
}
Copied!
这样我们除了对 x 排序,再对里层的 y 排序即可。
再来看第二个限制。其实看到上面的哈希表结构就比较清晰了,我们再对值排序即可。
总的来说,我们需要进行三次排序,分别是对 x 坐标,y 坐标 和 值。
那么时间复杂度是多少呢?我们来分析一下:
  • 哈希表最外层的 key 总个数是最大是树的宽度。
  • 哈希表第二层的 key 总个数是树的高度。
  • 哈希表值的总长度是树的节点数。
也就是说哈希表的总容量和树的总的节点数是同阶的。因此空间复杂度为 $O(N)$, 排序的复杂度大致为 $NlogN$,其中 N 为树的节点总数。

代码

  • 代码支持:Python,JS,CPP
Python Code:
1
class Solution(object):
2
def verticalTraversal(self, root):
3
seen = collections.defaultdict(
4
lambda: collections.defaultdict(list))
5
6
def dfs(root, x=0, y=0):
7
if not root:
8
return
9
seen[x][y].append(root.val)
10
dfs(root.left, x-1, y+1)
11
dfs(root.right, x+1, y+1)
12
13
dfs(root)
14
ans = []
15
# x 排序、
16
for x in sorted(seen):
17
level = []
18
# y 排序
19
for y in sorted(seen[x]):
20
# 值排序
21
level += sorted(v for v in seen[x][y])
22
ans.append(level)
23
24
return ans
Copied!
JS Code(by @suukii):
1
/**
2
* Definition for a binary tree node.
3
* function TreeNode(val) {
4
* this.val = val;
5
* this.left = this.right = null;
6
* }
7
*/
8
/**
9
* @param {TreeNode} root
10
* @return {number[][]}
11
*/
12
var verticalTraversal = function (root) {
13
if (!root) return [];
14
15
// 坐标集合以 x 坐标分组
16
const pos = {};
17
// dfs 遍历节点并记录每个节点的坐标
18
dfs(root, 0, 0);
19
20
// 得到所有节点坐标后,先按 x 坐标升序排序
21
let sorted = Object.keys(pos)
22
.sort((a, b) => +a - +b)
23
.map((key) => pos[key]);
24
25
// 再给 x 坐标相同的每组节点坐标分别排序
26
sorted = sorted.map((g) => {
27
g.sort((a, b) => {
28
// y 坐标相同的,按节点值升序排
29
if (a[0] === b[0]) return a[1] - b[1];
30
// 否则,按 y 坐标降序排
31
else return b[0] - a[0];
32
});
33
// 把 y 坐标去掉,返回节点值
34
return g.map((el) => el[1]);
35
});
36
37
return sorted;
38
39
// *********************************
40
function dfs(root, x, y) {
41
if (!root) return;
42
43
x in pos || (pos[x] = []);
44
// 保存坐标数据,格式是: [y, val]
45
pos[x].push([y, root.val]);
46
47
dfs(root.left, x - 1, y - 1);
48
dfs(root.right, x + 1, y - 1);
49
}
50
};
Copied!
CPP(by @Francis-xsc):
1
class Solution {
2
public:
3
struct node
4
{
5
int val;
6
int x;
7
int y;
8
node(int v,int X,int Y):val(v),x(X),y(Y){};
9
};
10
static bool cmp(node a,node b)
11
{
12
if(a.x^b.x)
13
return a.x<b.x;
14
if(a.y^b.y)
15
return a.y<b.y;
16
return a.val<b.val;
17
}
18
vector<node> a;
19
int minx=1000,maxx=-1000;
20
vector<vector<int>> verticalTraversal(TreeNode* root) {
21
dfs(root,0,0);
22
sort(a.begin(),a.end(),cmp);
23
vector<vector<int>>ans(maxx-minx+1);
24
for(auto xx:a)
25
{
26
ans[xx.x-minx].push_back(xx.val);
27
}
28
return ans;
29
}
30
void dfs(TreeNode* root,int x,int y)
31
{
32
if(root==nullptr)
33
return;
34
if(x<minx)
35
minx=x;
36
if(x>maxx)
37
maxx=x;
38
a.push_back(node(root->val,x,y));
39
dfs(root->left,x-1,y+1);
40
dfs(root->right,x+1,y+1);
41
}
42
};
Copied!
复杂度分析
  • 时间复杂度:$O(NlogN)$,其中 N 为树的节点总数。
  • 空间复杂度:$O(N)$,其中 N 为树的节点总数。