大家好,我是 方圆 。本篇关于二叉树的后序遍历,依然是由题目来展开,如果大家想要找刷题路线的话,可以参考 Github: LeetCode。
后序遍历
后序遍历对节点的操作顺序是 "左右根" ,这种遍历方式会 先处理当前节点的左右子树,再去处理当前节点 ,也就是说后序遍历会在递归结束,由下向上回溯时处理各个节点,模板如下:
java
private void postOrder(TreeNode node) {
if (node == null) {
return;
}
postOrder(node.left);
postOrder(node.right);
// do something...
}
我们先来看几道简单的题目:
求二叉树的最大深度,我们可以使用后序遍历 先获取到当前节点的左右子树节点的高度,取其中的最大值加一,即为当前节点的高度,叶子节点的高度为零,遍历结束后即可获得根节点到叶子节点的最大高度,题解如下:
java
public int maxDepth(TreeNode root) {
if (root == null) {
return 0;
}
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left, right) + 1;
}
本题和上一题大同小异,判断二叉树是否平衡我们只需判断节点的左右子树高度差是否大于一即可,题解如下:
java
public boolean isBalanced(TreeNode root) {
if (root == null) {
return true;
}
return depth(root) != -1;
}
private int depth(TreeNode node) {
if (node == null) {
return 0;
}
int left = depth(node.left);
int right = depth(node.right);
if (left == -1 || right == -1 || Math.abs(left - right) > 1) {
return -1;
}
return Math.max(left, right) + 1;
}
相关练习
接下来是一些相对复杂的题目:
寻找二叉树指定节点的最近公共祖先可以分两种情况:如果两节点在同一子树中,最近公共祖先为其中"先找到"的节点;如果两节点分别在左右子树中,那么最近公共祖先为当前节点,题解如下:
java
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || p.val == root.val || q.val == root.val) {
return root;
}
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if (left != null && right != null) {
return root;
}
return left == null ? right : left;
}
二叉树剪枝需要将节点值全是 0 的子树移除,那么我们可以去分别判断左右子树是否有 0,为 0 的话我们直接将其剪枝,需要注意根节点为 0 的特殊情况,题解如下:
java
public TreeNode pruneTree(TreeNode root) {
if (root == null) {
return null;
}
int res = doPruneTree(root);
if (res == 0) {
return null;
}
return root;
}
private int doPruneTree(TreeNode node) {
if (node == null) {
return 0;
}
int left = doPruneTree(node.left);
int right = doPruneTree(node.right);
if (left == 0) {
node.left = null;
}
if (right == 0) {
node.right = null;
}
return node.val + left + right;
}
题目要求间隔的节点才能抢,那么我们需要将抢当前节点和不抢当前节点的收益都计算出来,取其中较大的收益,需要注意的是由于多次调用递归方法,所以对同一节点会出现重复遍历的情况,这将会导致超时,所以我们需要创建备忘录记录已经抢过节点对应的收益,来减少递归次数,题解如下:
java
HashMap<TreeNode, Integer> memo = new HashMap<>();
public int rob(TreeNode root) {
if (root == null) {
return 0;
}
if (memo.containsKey(root)) {
return memo.get(root);
}
// 抢当前节点
int val1 = root.val;
if (root.left != null) {
val1 += rob(root.left.left);
val1 += rob(root.left.right);
}
if (root.right != null) {
val1 += rob(root.right.left);
val1 += rob(root.right.right);
}
// 不抢当前节点
int val2 = rob(root.left) + rob(root.right);
int max = Math.max(val1, val2);
memo.put(root, max);
return max;
}
路径问题
非根节点路径问题(路径可以不经过根节点)使得每个节点都有可能成为我们要求的结果值,所以在处理每个节点时需要计算当前节点作为最终结果的值(累加当前节点和左右子树的路径值)和当前节点左右路径的最值,前者用来根据题意记录最终结果,后者作为左右路径的最值返回,该返回值会成为回溯过程中其他节点的左/右路径值,通过下面两题来练习一下:
首先我们先把后序遍历的模板写上,在根据题意求解。题目要求节点值相同的组成一条路径,那么在向上回溯时需要判断当前节点和左右节点值是否相同,并计算当前节点作为路径"转折点"时的值 cur 和当前左右路径的最值 max,通过全局变量保存所有节点作为路径"转折点"时的最大值,题解如下:
java
int res;
public int longestUnivaluePath(TreeNode root) {
this.res = 0;
doLongestUnivaluePath(root);
return res;
}
private int doLongestUnivaluePath(TreeNode node) {
if (node == null) {
return 0;
}
int left = doLongestUnivaluePath(node.left);
int right = doLongestUnivaluePath(node.right);
int cur = 0, max = 0;
if (node.left != null && node.left.val == node.val) {
max = left + 1;
cur = left + 1;
}
if (node.right != null && node.right.val == node.val) {
cur += right + 1;
max = Math.max(max, right + 1);
}
res = Math.max(res, cur);
return max;
}
虽然本题为困难的题目,但是思路和上一题基本一致。题目要求最大路径和,那么我们在回溯向上求解时,只需判断左/右子树的值是否大于零即可,大于零的我们将其累加到结果上,同样地,我们仍然需要计算当前节点作为路径"转折点"时的结果值和左/右路径的最值,题解如下:
java
int res = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
doMaxPathSum(root);
return res;
}
private int doMaxPathSum(TreeNode node) {
if (node == null) {
return 0;
}
int left = doMaxPathSum(node.left);
int right = doMaxPathSum(node.right);
int cur = node.val, max = node.val;
if (left > 0) {
cur += left;
max += left;
}
if (right > 0) {
cur += right;
max = Math.max(max, node.val + right);
}
res = Math.max(res, cur);
return max;
}