携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第32天,点击查看活动详情
题目链接:662. 二叉树最大宽度
题目描述
给你一棵二叉树的根节点 root ,返回树的 最大宽度 。
树的 最大宽度 是所有层中最大的 宽度 。
每一层的 宽度 被定义为该层最左和最右的非空节点(即,两个端点)之间的长度。将这个二叉树视作与满二叉树结构相同,两端点间会出现一些延伸到这一层的 null 节点,这些 null 节点也计入长度。
题目数据保证答案将会在 32 位 带符号整数范围内。
提示:
- 树中节点的数目范围是
[1, 3000] -100 <= Node.val <= 100
示例 1:
输入: root = [1,3,2,5,3,null,9]
输出: 4
解释: 最大宽度出现在树的第 3 层,宽度为 4 (5,3,null,9) 。
示例 2:
输入:root = [1,3,2,5,null,null,9,6,null,7]
输出:7
解释:最大宽度出现在树的第 4 层,宽度为 7 (6,null,null,null,null,null,7) 。
示例 3:
输入: root = [1,3,2,5]
输出: 2
解释: 最大宽度出现在树的第 2 层,宽度为 2 (3,2) 。
整理题意
题目给定一颗二叉树的根节点 root,让我们找到这颗二叉树的 最大宽度:题目规定这个最大宽度为二叉树中所有层中最左边的节点和最右边的节点所包含的宽度,即使中间有 null 节点也算在宽度里面。
题目数据保证答案将会在
32位 带符号整数范围内。
解题思路分析
对于每一层的最左边的节点和最右边的节点计算宽度,我们可以想到使用二叉树的标号思路,也就是给每个节点进行标号,如果当前节点的序号为 x,那么当前节点的左儿子节点的序号为 2x,右儿子节点的序号为 2x + 1。
那么计算每一层的宽度就用最右边节点的序号减去最左边节点的序号加一即可。
具体实现
- 采用广度优先搜索
BFS对二叉树进行层序遍历更符合题意,队列中存放节点指针和序号值; - 记录层序遍历过程中每一层最左边的元素和最右边的元素节点;
- 用
right记录最右边节点的序号,left记录最左边节点的序号,那么当前层的宽度为:right - left + 1; - 在层序遍历的过程中用
ans记录所有层的最大宽度,维护ans最大值即可; - 最后输出
ans。
注意:
该题需要注意溢出问题,由于题目数据保证答案将会在 32 位 带符号整数范围内,但是使用 long long int 来保存数据还是会导致溢出,此时我们采用 unsigned long long int 来存储就不会溢出,不是说非要 unsigned long long,理论上 ull 的最大值也不够,关键 是这里借助 无符号整型 在溢出的时候自动根据 32 位,或者 64 位取模,换成unsigned int一样能过,这是因为取模后同一层的 index 会出现前大后小的情况,但因为是无符号整型,小减大后结果仍然正确。
这里需要注意为啥取模后是正确的,因为题目数据保证答案将会在
32位 带符号整数范围内,然而我们的序号是会溢出的,甚至unsigned long long也会溢出,但是每一层的最大宽度不会超过32位 带符号整数范围,也就是每次溢出不会超过32位,所以使用unsigned int也是可以通过的。需要理解序号和宽度的区别。核心就在于 关键 是这里借助 无符号整型 在溢出的时候自动根据32位,或者64位取模。
通俗解释这个溢出后取模任然能够保证答案正确的原因: 就像我们生活中的钟表一样,钟表最多表示 12 个小时,当我们到达 13 ~ 23 之间的小时后会从新从以 1 ~ 12 表示,但我们任然能够算出经过的时间。这里钟表的 12 小时就相当于题目数据保证答案将会在 32 位 带符号整数范围内,每次最多 +12,而不会超过 12,这样即使溢出,也只会导致前大后小的情况,但我们任然能够判断出是由大数字溢出到达的小数字。
复杂度分析
- 时间复杂度:,其中 是二叉树的节点个数。需要遍历所有节点
- 空间复杂度:。广度优先搜索的空间复杂度最多为 。
代码实现
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int widthOfBinaryTree(TreeNode* root) {
if(!root) return 0;
// 当根节点不为空的时候宽度至少为 1
unsigned long long int ans = 1;
// 队列存放节点指针和序号,如果当前节点序号为 x,左 2x,右 2x + 1
queue<pair<TreeNode*, unsigned long long int>> que;
while(que.size()) que.pop();
que.push(make_pair(root, 1));
while(que.size()){
int n = que.size();
unsigned long long int left = 0, right = 0;
for(int i = 0; i < n; i++){
pair<TreeNode*, unsigned long long int> now = que.front();
que.pop();
if(left == 0) left = now.second;
right = now.second;
if(now.first->left){
que.push(make_pair(now.first->left, 2 * now.second));
}
if(now.first->right){
que.push(make_pair(now.first->right, 2 * now.second + 1));
}
}
// 取模后同一层的index会出现前大后小的情况,但因为是无符号整型,小减大后结果仍然正确
ans = max(ans, right - left + 1);
}
return ans;
}
};
总结
- 该题的核心在于对二叉树的每个节点进行标号处理以及二叉树的层序遍历,其次难点在于标号溢出的处理,这里 关键 是借助 无符号整型 在溢出的时候自动根据
32位,或者64位取模,同时注意到题目数据保证答案将会在32位 带符号整数范围内,所以我们采用unsigned int也是可以通过的。 - 测试结果:
结束语
真正的勇敢,不是从不害怕,而是充分了解困难和风险后,选择继续前行;是知道人生路上总有艰难坎坷,依然有勇气拥抱生活、笑迎挫折。每一个迎风向前的人,都是自己的英雄。新的一天,加油!