题目描述

给定一个非空二叉树,返回其最大路径和。

本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

示例 1:

输入: [1,2,3]

       1
      / \
     2   3

输出: 6

示例 2:

输入: [-10,9,20,null,null,15,7]

   -10
   / \
  9  20
    /  \
   15   7

输出: 42

https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/

解法1 – DFS(超时)

刚遇到题目时没有什么思路,根据题目描述“本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。”,发现这很像图数据结构的DFS算法。

图1 从节点7开始DFS

我们可以把树转换为无向图,这通过向TreeNode数据结构添加Parent字段就能够实现。这样,每个节点有三个临接点分别是左子树、右子树、父节点。

static class TreeNodeEx {
        TreeNodeEx parent;
        TreeNodeEx left, right;
        int val;

        TreeNodeEx(int val) {
            this.val = val;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            TreeNodeEx that = (TreeNodeEx) o;
            return val == that.val &&
                    Objects.equals(left, that.left) &&
                    Objects.equals(right, that.right);
        }

        @Override
        public int hashCode() {
            return Objects.hash(super.hashCode(), left, right, val);
        }
}

// 将原有的树转化为带有父节点的树
TreeNodeEx copyTree(TreeNode root, List<TreeNodeEx> startPoints) {
    if (root == null)
        return null;
    TreeNodeEx newRoot = new TreeNodeEx(root.val);

    newRoot.left = copyTree(root.left, startPoints);
    if (newRoot.left != null)
        newRoot.left.parent = newRoot;

    newRoot.right = copyTree(root.right, startPoints);
    if (newRoot.right != null)
        newRoot.right.parent = newRoot;

    if (newRoot.val > 0)
        startPoints.add(newRoot);
    else
        result = Math.max(result, newRoot.val);

    return newRoot;
}

我们从树中寻找val大于0的节点作为DFS的起点,从所有的起点出发遍历完这棵树,肯定能找到包含答案的路径。我们累积走过的每一个定点的值,用当前累积的值更新全局变量resultresult就存放了最终的答案。

void DFS(TreeNodeEx root, int pathSum) {
    visited.add(root);
    pathSum += root.val;
    result = Math.max(result, pathSum);

    if (root.left != null &amp;&amp; !visited.contains(root.left)) {
        DFS(root.left, pathSum);
    }

    if (root.right != null &amp;&amp; !visited.contains(root.right)) {
        DFS(root.right, pathSum);
    }

    if (root.parent != null &amp;&amp; !visited.contains(root.parent)) {
        DFS(root.parent, pathSum);
    }
    visited.remove(root);
}

Set<TreeNodeEx> visited;
int result = Integer.MIN_VALUE;

public int maxPathSum(TreeNode root) {
    if (root == null)
        return 0;

    List<TreeNodeEx> startPoints = new ArrayList<>();

    // 复制原来的树结构,并加入parent字段;与此同时过滤val>0的节点作为DFS的根节点
    copyTree(root, startPoints);
    visited = new HashSet<>();

    for (TreeNodeEx startPt : startPoints) {
        DFS(startPt, 0);
        visited.clear();
    }
    int tmp = result;
    result = Integer.MIN_VALUE;

    return tmp;
}

很遗憾,上面的方法虽然能够找到正确的答案,但是效率太低。我一时也没有找到这种方法的优化思路,先记录下来。

解法2

方法2来自于该题目的评论区。思路是:节点n向父节点汇报以下三者中的最大值;如果节点n是叶节点则仅汇报节点n自身的值。

  • 节点n的值
  • 节点n的值+节点n左子树汇报的值
  • 节点n的值+节点n右子树汇报的值

我们以图2为例,红色字体给出的是节点向其父节点按照上述规则汇报的值。注意,图2与图1不相同

图2 向父节点汇报值

有了上面的图,我们把图中每个顶点“臆想”成该顶点就是整颗树的根节点,而忽略掉该节点之上的其他节点。然后我们对每个“臆想”的根节点,按照以下规则计算出的最大值。

  • 根节点的值
  • 根节点的值+左子树汇报的值
  • 根节点的值+右子树汇报的值
  • 根节点的值+左子树汇报的值+右子树汇报的值

因为我们把每个节点“臆想”成根节点,我们才会有上面规则的第4条。需要注意的是,该问题的解并不一定来源于整颗树的根节点。我们想个反例,把图2的根节点5的值调整为-∞,那么解应该来源于节点8.

我们可以把上面的操作与向父节点汇报值的过程融合,并不一定要实际的存储汇报的值,然后对树再扫描一遍。

class Solution {
    int result = Integer.MIN_VALUE;
    public int maxPathSum(TreeNode root) {
        helper(root);
        // 清空result,防止副作用
        int tmp = result;
        result = Integer.MIN_VALUE;

        return tmp;
    }

    public int helper(TreeNode root) {
        if (root == null)
            return 0;

        // 以root为根节点的角度来看

        int lVal = helper(root.left);
        int rVal = helper(root.right);

        int cond1 = root.val;
        int cond2 = root.val + lVal;
        int cond3 = root.val + rVal;

        // 看到下面两行代码,我们把root臆想成整颗树的根,那么我们就应该汇总这4种情况,因为最终的解可能来自于该节点。
        int cond4 = root.val + lVal + rVal;
        result = Math.max(result, Math.max(Math.max(cond1, cond2), Math.max(cond3, cond4)));

        // 我们还需要向父节点汇报三种情况的值
        return Math.max(cond1, Math.max(cond2, cond3));
    }
}
pwrliang Algorithms, Tree ,

Leave a Reply

Your email address will not be published. Required fields are marked *