之前已经介绍过了二叉树的前中后序遍历及层序遍历,这是解决所有二叉树问题的手段。上一期也提到过,很多题既可以用前中后序遍历去做也可以用层序遍历去做。本期就介绍一下例题,分别展示两种做法。
1. 二叉树的右视图
199. 二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
1.1 方法1:前序遍历
因为右视图的节点可能分布在各个位置,即处于某一层最右侧的节点有可能在左子树,也有可能在右子树,有可能在左子树的左子树,也有可能在右子树的左子树......因此,采用前序遍历的方式也需要遍历整棵树,时间复杂度为O(n)。
所以,前序遍历的想法就是,用一个高度值curHeight记录你当前所在层的高度,用一个List去收集每一层的最右侧的值。遍历的时候先把List填满,后序如果有更深的深度再往List中放值。后面遍历的时候再在相同高度发现List中有值,说明List中存的已经不是最右侧的了,就更新这个值。
图解如下:
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
dfs(res, root, 0);
return res;
}
private void dfs(List<Integer> res, TreeNode root, int curHeight) {
if (res.size() == curHeight) {
res.add(root.val);
} else {
res.set(curHeight, root.val);
}
if (root.left != null) {
dfs(res, root.left, curHeight + 1);
}
if (root.right != null) {
dfs(res, root.right, curHeight + 1);
}
}
1.2 方法2:层序遍历
层序遍历与上一期代码类似,自行理解。
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.push(root);
while (!deque.isEmpty()) {
int size = deque.size();
while (size-- > 0) {
TreeNode pop = deque.pop();
if(size == 0) {
res.add(pop.val);
}
if (pop.left != null) {
deque.addLast(pop.left);
}
if (pop.right != null) {
deque.addLast(pop.right);
}
}
}
return res;
}
2. 二叉树的层平均值
637. 二叉树的层平均值
给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5
以内的答案可以被接受
2.1 方法1:前序遍历
解法思路同右视图,即在遍历二叉树的每一条路径的时候用一个List记录对应位置的值,只不过右视图是每当发现新值就更新,本题是每当在对应深度发现新值就在List对应位置给做加和,同时再用一个List在对应深度记录当前深度的节点数。最后获取两个List,每个相同位置元素做除法,就得到了层平均值。
public List<Double> averageOfLevels(TreeNode root) {
List<Double> sums = new ArrayList<>();
if (root == null) {
return sums;
}
List<Integer> nums = new ArrayList<>();
dfs(sums, root, nums, 0);
List<Double> res = new ArrayList<>();
for (int i = 0; i < nums.size(); i++) {
res.add(i, sums.get(i) / nums.get(i));
}
return res;
}
private void dfs(List<Double> sums, TreeNode root, List<Integer> nums, int curHeight) {
if (sums.size() == curHeight) {
sums.add(root.val * 1.0);
nums.add(1);
} else {
sums.set(curHeight, sums.get(curHeight) + root.val);
nums.set(curHeight, nums.get(curHeight) + 1);
}
if (root.left != null) {
dfs(sums, root.left, nums, curHeight + 1);
}
if (root.right != null) {
dfs(sums, root.right, nums, curHeight + 1);
}
}
2.2 方法2:层序遍历
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
Deque<TreeNode> deque = new LinkedList();
deque.push(root);
while (!deque.isEmpty()) {
int size = deque.size();
Double sum = 0d;
int nums = size;
while (size-- > 0) {
TreeNode pop = deque.pop();
sum += pop.val;
if (pop.left != null) {
deque.addLast(pop.left);
}
if (pop.right != null) {
deque.addLast(pop.right);
}
}
res.add(sum / nums);
}
return res;
}
3. 二叉树层最大值
515. 在每个树行中找最大值
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
3.1 方法一:前序遍历
还是熟悉的配方用一个curHeight记录当前高度,然后在遍历的时候一旦到了相同的高度,就对比List中存的值和现在的值哪个,如果比存的值大,就更新List对应位置的值。
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
dfs(res, root, 0);
return res;
}
private void dfs(List<Integer> res, TreeNode root, int curHeight) {
if (res.size() == curHeight) {
res.add(root.val);
} else {
res.set(curHeight, Math.max(res.get(curHeight), root.val));
}
if (root.left != null) {
dfs(res, root.left, curHeight + 1);
}
if (root.right != null) {
dfs(res, root.right, curHeight + 1);
}
}
3.2 方法二:层序遍历
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) {
return res;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.push(root);
while (!deque.isEmpty()) {
int size = deque.size();
int maxValue = deque.peek().val;
while (size-- > 0) {
TreeNode pop = deque.pop();
maxValue = Math.max(maxValue, pop.val);
if (pop.left != null) {
deque.addLast(pop.left);
}
if (pop.right != null) {
deque.addLast(pop.right);
}
}
res.add(maxValue);
}
return res;
}
4. 填充每个节点的下一个右侧节点指针
116. 填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。初始状态下,所有 next 指针都被设置为 NULL
。
117. 填充每个节点的下一个右侧节点指针 II
给定一个二叉树:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL
。初始状态下,所有 next 指针都被设置为 NULL
。
两道题解法几乎一样,在这里给出通用的代码(一套代码打两个)。
4.1 方法一:前序遍历
思路与之前相似,记录高度,每次遍历到曾经遍历过的高度,就让List中存的next指向当前节点,并把当前节点更新到List中以便下一次的引用指向。
public static Node connect(Node root) {
if (root == null) {
return root;
}
Node cur = root;
List<Node> records = new ArrayList<>();
dfs(records, cur, 0);
return root;
}
private static void dfs(List<Node> records, Node root, int curHeight) {
if(root == null) {
return;
}
if (records.size() == curHeight) {
records.add(root);
} else {
Node node = records.get(curHeight);
node.next = root;
records.set(curHeight, root);
}
dfs(records, root.left, curHeight + 1);
dfs(records, root.right, curHeight + 1);
}
4.2 方法二:层序遍历
public Node connect(Node root) {
Deque<Node> deque = new LinkedList<>();
if (root != null) {
deque.push(root);
}
while (!deque.isEmpty()) {
int size = deque.size();
while (size-- > 0) {
Node node = deque.pollFirst();
if (size != 0) {
node.next = deque.peek();
} else {
node.next = null;
}
if (node.left != null) {
deque.addLast(node.left);
}
if (node.right != null) {
deque.addLast(node.right);
}
}
}
return root;
}
这个层序遍历,由于116题中,说了是完美二叉树,所以做116的时候可以不用写两个判断,写一个也是可以的。上面代码是一个通用的解法,怎么写都可以。
if (pop1.left != null) {
deque.addLast(pop1.right);
deque.addLast(pop1.left);
}
5. 二叉树的最大深度
104. 二叉树的最大深度
给定一个二叉树 root
,返回其最大深度。二叉树的最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
5.1 方法一:后序遍历
这道题用递归是最简单的,为什么用后序遍历呢?在这里说一下思路,最好的解决办法就是:
(1)求取当前节点左子树的深度;
(2)再求取当前节点右子树的深度;
(3)然后比较两科子树的深度,取最深的加1,就是当前节点为头节点所在子树的最大深度。
这不就是“左右中”——后序遍历吗。
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int l = maxDepth(root.left);
int r = maxDepth(root.right);
return Math.max(l, r) + 1;
}
5.2 方法二:层序遍历
层序的解决方法就简单粗暴了,我直接一层一层去遍历,直到下一层一个节点都没有了,每遍历一层就+1。
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.push(root);
int height = 0;
while (!deque.isEmpty()) {
int size = deque.size();
height++;
while (size-- > 0) {
TreeNode pop = deque.pop();
if (pop.left != null) {
deque.addLast(pop.left);
}
if (pop.right != null) {
deque.addLast(pop.right);
}
}
}
return height;
}
6. 二叉树的最小深度
111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量
6.1 方法一:后序遍历
首先需要这段代码限制一下null为空的情况,root为null深度就为0,返回就好了。
if(root == null) {
return 0;
}
当前节点左节点为null,右节点也为null,此时这个节点的高度就是1,然后还是采用最大深度的思想,“中”用来收集左右子树最小的深度。
public int minDepth(TreeNode root) {
if(root == null) {
return 0;
}
if (root.left == null && root.right == null) {
return 1;
}
int l = Integer.MAX_VALUE;
if (root.left != null) {
l = minDepth(root.left);
}
int r = Integer.MAX_VALUE;
if (root.right != null) {
r = minDepth(root.right);
}
return Math.min(l, r) + 1;
}
6.2 方法二:层序遍历
在层序遍历的时候,一旦发现哪个节点的左节点为null,右节点也为null,就说明这个节点是叶子节点了。因为是一层一层的遍历,所以一旦第一次碰到叶子节点,这个深度一定就是最小的,直接返回此时记录的深度就好。
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.push(root);
int height = 0;
while (!deque.isEmpty()) {
int size = deque.size();
height++;
while (size-- > 0) {
TreeNode pop = deque.pop();
if (pop.left == null && pop.right == null) {
return height;
}
if (pop.left != null) {
deque.addLast(pop.left);
}
if (pop.right != null) {
deque.addLast(pop.right);
}
}
}
return height;
}