如何在线性时间内求二叉树之字形交替和路径的最大值?
结论
一次后序遍历即可解决这个问题,时间复杂度为 O(n),额外空间复杂度为 O(h),其中 h 是树高。对每个节点,分别记录第一步向左和第一步向右时,交替和的最小值与最大值。遍历完成后,所有节点状态中的最大值就是全树答案。
最小值同样需要保留。父节点接上子路径时,交替和会变成 node.val - childPathSum,因此子路径和越小,父路径和可能越大。
状态与转移
定义 minL、maxL,表示从当前节点出发,下一步向左时(也可以在当前节点结束)路径交替和的最小值与最大值。minR、maxR 表示下一步向右时的对应值。每个方向的状态都包含只经过当前节点的路径,也就是在当前节点停止。
第一步向左到达左孩子后,下一步只能向右,所以左方向状态根据左孩子的右方向状态计算:
maxL = max(node.val, node.val - left.minR)
minL = min(node.val, node.val - left.maxR)
向右时同理,根据右孩子的左方向状态计算:
maxR = max(node.val, node.val - right.minL)
minR = min(node.val, node.val - right.maxL)
如果孩子不存在,对应状态就保持为 node.val,表示路径在当前节点结束。处理每个节点时,用 maxL 和 maxR 更新全局最大值,因此路径起点可以是树中的任意节点。
Java 示例
下面假设 Node 包含 int val、Node left 和 Node right 字段,且输入树非空。状态使用 long,以免路径累加超出 int 范围。
class State {
long minL, maxL;
long minR, maxR;
State(long value) {
minL = maxL = value;
minR = maxR = value;
}
}
class Solver {
private long best;
public long maxZigZagSum(Node root) {
if (root == null) {
throw new IllegalArgumentException("root must not be null");
}
best = Long.MIN_VALUE;
dfs(root);
return best;
}
private State dfs(Node node) {
if (node == null) {
return null;
}
State left = dfs(node.left);
State right = dfs(node.right);
long value = node.val;
State current = new State(value);
if (left != null) {
// 当前节点向左,左孩子之后必须向右。
current.maxL = Math.max(value, value - left.minR);
current.minL = Math.min(value, value - left.maxR);
}
if (right != null) {
// 当前节点向右,右孩子之后必须向左。
current.maxR = Math.max(value, value - right.minL);
current.minR = Math.min(value, value - right.maxL);
}
best = Math.max(best, Math.max(current.maxL, current.maxR));
return current;
}
}
如果接口允许传入空树,应按照调用方的约定为 root == null 指定返回值。空树没有有效路径。
为什么单个“最佳起始路径”状态不够
只记录从节点出发的最大交替和,不足以正确地合并到父节点。父节点的转移是 node.val - childPathSum,子路径和越小,父路径和反而可能越大。只保留子路径的最大值,会丢掉计算父节点最大值所需的信息。因此,至少要同时记录最小值和最大值,并按下一步方向区分状态。
题目中的代码从根节点出发,沿固定的左右交替方向递归,因此只会考虑以根为起点的路径。即使分别尝试两种初始方向,起点仍然是根,无法覆盖树中间的节点。若从每个节点重新启动递归,时间复杂度可能达到 O(n²)。后序遍历只需在每个节点计算常数个状态,再将结果传给父节点。
备注:内容仅供参考。
