2023-05-16 刷题
LeetCode 662. 二叉树最大宽度
思路:节点编号 + BFS。时间复杂度:O(n),空间复杂度:O(n)
-
利用完全二叉树(以及堆的顺序存储)编号的规则,根节点编号为1,左孩子是2,右孩子是3. 对于任意一个节点,如果它的编号是i, 那么它的左孩子的编号是2*i, 右孩子的编号是2*i + 1. 因为有的节点没有左右孩子,因此,需要把编号和节点保存在一起。可以使用Pair结构。
-
每层节点最大宽度是BFS逐层遍历得到的节点列表中,最右边的编号 - 最左边的编号 + 1。 所以这里可以使用双端队列Deque这个接口,和ArrayDeque这个队列实现类。
-
BFS有两种写法,一种是一层while循环,一次处理一个节点。另外一种是两层循环,内层循环遍历当前一层。外层循环,对应二叉树的层数(高度)。显然,这个题目适合用逐层遍历的写法。
-
分析复杂度:二叉树中的每个节点进入队列一次,出队列一次,所以总共遍历2n次,时间负责度是O(n). BFS 遍历保存的队列,最多的情况是叶子那一层,节点个数超过 n/2(满二叉树下,它是n/2上取整),因此空间复杂度是 O(n).
class Solution {
public int widthOfBinaryTree(TreeNode root) {
Deque<NodePair> que = new ArrayDeque<>();
que.offer(new NodePair(root, 1));
int ans = 1;
while (!que.isEmpty()) {
int curWidth = que.getLast().index - que.getFirst().index + 1;
ans = Math.max(ans, curWidth);
for (int i = que.size(); i > 0; i--) {
NodePair cur = que.poll();
int left = cur.index * 2;
TreeNode node = cur.node;
if (node.left != null) {
que.offer(new NodePair(node.left, left));
}
if (node.right != null) {
que.offer(new NodePair(node.right, left + 1));
}
}
}
return ans;
}
static class NodePair {
TreeNode node;
int index;
public NodePair(TreeNode node, int index) {
this.node = node;
this.index = index;
}
}
}
5-17 更新:另一种思路,DFS实现
这个题目竟然还可以通过DFS来做。只要确定了使用编号的思想,那么遍历除了BFS,还应该想到DFS。
问题转换成,在DFS的情况下,怎么知道一层的最左和最右的节点?
- DFS遍历时某一层的情况,其实也是从左到右。不过是穿插着进行的。
- 可以使用一个Map记录每一层的最左边的节点。第一次添加到这一层的时,一定是最左节点。
- 对于某层的最右节点,我们可以在遍历时,直接计算width,然后返回最大结果(或者更新全局最大值)
代码如下:
class Solution {
Map<Integer, Integer> levelMap;
public int widthOfBinaryTree(TreeNode root) {
levelMap = new HashMap<>();
return dfs(root, 1, 1);
}
int dfs(TreeNode root, int level, int id) {
if (root == null) {
return 0;
}
levelMap.putIfAbsent(level, id); // 第一次遍历某一层第一个节点一定是该层最左节点
int leftAns = dfs(root.left, level + 1, id * 2);
int rightAns = dfs(root.right, level + 1, id * 2 + 1);
int curAns = id - levelMap.get(level) + 1;
return Math.max(curAns, Math.max(leftAns, rightAns));
}
}
复杂度分析:
- 时间:O(n)每个节点只遍历一遍。
- 空间:O(n) Map最大保存n个元素,栈最大的深度也是n(当二叉树退化成单枝的情况下)
DFS实现二叉树层次遍历
所以同样地,我们可以使用DFS来实现二叉树的层次遍历。
import java.util.ArrayList;
import java.util.List;
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
public class BinaryTreeDFS {
public static List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
dfs(root, 0, ans); // for convenient, level begins with 0
return ans;
}
static void dfs(TreeNode root, int level, List<List<Integer>> ans) {
if (root != null) {
// when visit the leftmost node of one level, add placeholder list
if (level == ans.size()) {
ans.add(new ArrayList<>());
}
ans.get(level).add(root.val);
dfs(root.left, level + 1, ans);
dfs(root.right, level + 1, ans);
}
}
public static void main(String[] args) {
TreeNode root = new TreeNode(3);
root.left = new TreeNode(9);
root.right = new TreeNode(20);
root.left.left = new TreeNode(1);
root.right.left = new TreeNode(15);
root.right.right = new TreeNode(7);
List<List<Integer>> levelOrderTraversal = levelOrder(root);
for (List<Integer> level : levelOrderTraversal) {
System.out.println(level);
}
}
}
// time: O(n),每个节点都只遍历一次。
// space: O(n) 使用Map最多保存n个元素,栈最大深度也是n。(当二叉树退化成单支时,即每个节点只有一个孩子)
收获总结
- 学习到在BFS做层次遍历时,除了最常见的保存TreeNode到队列中,还可以保存更多的信息,例如节点编号
- 看到中文官方题解,给出的BFS实现,没有使用Queue,而是使用一个List记录当前层,一个临时List记录下一层,然后在外层循环更新当前层的List。
- 学会用DFS来做层次遍历的思想
标签:
面试
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~