【问题标题】:Do not understand the solution for the Binary Tree Maximum Path Sum problem不明白二叉树最大路径和问题的解法
【发布时间】:2021-03-17 17:25:30
【问题描述】:

GeeksforGeeks 网站针对二叉树的最大路径和问题提出了a solution。问题如下:

给定一棵二叉树,求最大路径和。路径可能开始并且 在树中的任何节点处结束。

解决方案的核心如下:

int findMaxUtil(Node node, Res res) 
{ 
  
    if (node == null) 
        return 0; 
  
    // l and r store maximum path sum going through left and 
    // right child of root respectively 
    int l = findMaxUtil(node.left, res); 
    int r = findMaxUtil(node.right, res); 
  
    // Max path for parent call of root. This path must 
    // include at-most one child of root 
    int max_single = Math.max(Math.max(l, r) + node.data, 
                              node.data); 
  
  
    // Max Top represents the sum when the Node under 
    // consideration is the root of the maxsum path and no 
    // ancestors of root are there in max sum path 
    int max_top = Math.max(max_single, l + r + node.data); 
  
    // Store the Maximum Result. 
    res.val = Math.max(res.val, max_top); 
  
    return max_single; 
} 

int findMaxSum() { 
    return findMaxSum(root); 
 } 
  
// Returns maximum path sum in tree with given root 
int findMaxSum(Node node) { 
  
    // Initialize result 
    // int res2 = Integer.MIN_VALUE; 
    Res res = new Res(); 
    res.val = Integer.MIN_VALUE; 
  
    // Compute and return result 
    findMaxUtil(node, res); 
    return res.val; 
} 

Res 有如下定义:

 class Res { 
    public int val; 
}

我对这些代码行背后的推理感到困惑:

int max_single = Math.max(Math.max(l, r) + node.data, node.data);  

int max_top = Math.max(max_single, l + r + node.data); 

res.val = Math.max(res.val, max_top); 

return max_single; 

我相信上面的代码遵循这个逻辑,但我不明白为什么这个逻辑是正确或有效的

对于每个节点,最大路径可以通过四种方式 节点:

  1. 仅节点
  2. 通过左孩子 + 节点的最大路径
  3. 通过右子节点 + 节点的最大路径
  4. 通过左孩子的最大路径 + 节点 + 通过右孩子的最大路径

特别是,当我们变量res.val包含我们感兴趣的答案时,我不明白为什么在函数findMaxUtil中返回max_single。以下原因在网站上给出但我不明白了:

需要注意的重要一点是,每个子树的根都需要返回 最大路径总和,使得最多包含一个根的孩子。

有人可以解释一下解决方案的这一步吗?

【问题讨论】:

  • 使用这种方法是因为findMaxUtil 是一个递归函数。这里,res 用于跟踪调用findMaxUtil 时将传递的整体最大值,随后将用于在传递树节点期间与路径总和进行比较。其他方法是在语言支持时将res 作为全局变量。
  • @akuzminykh 给定的解决方案通过Leetcode tests
  • 逻辑表明最大路径不必总是从根开始。最大路径总和可能是它的子树之一。
  • @a_sid,每经过一个节点,有5个新的max-sum-path候选,即left+current noderight+current nodecurrent nodeleft+right+current node和max-sum -路径或res。比较这 5 个值将产生新的 max-sum-path/res。如果我们观察,这里考虑current node,因为一个节点可以有负值,所以current node数据仍有可能大于current node+leftcurrent node+right
  • @a_sid 例如,如果您遵循 Java 命名约定,您的 max_single 将是 maxSingle。

标签: java algorithm binary-tree


【解决方案1】:

老实说,我认为该网站上的描述非常不清楚。我会尽力让你相信算法背后的推理。

我们有一个二叉树,在节点上有值:

我们正在那棵树中寻找一条路径,一条连接的节点链。

因为它是一棵有向树,任何非空路径由一个深度最低的节点(即路径中离树根最近的节点)、零个或多个路径组成节点下降到最低深度节点的左侧,以及零个或多个节点的路径下降到最低深度节点的右侧。特别是,在树的某处,有一个节点是最大路径中的最低深度节点。 (实际上,可能有不止一条这样的路径具有相同的价值,并且它们可能都有自己独特的最低深度节点。这很好。只要至少有一个,这才是最重要的。)

(我在图中使用了“最高”,但我的意思是“最低深度”。为了清楚起见,任何时候我使用“深度”或“降序”我都在谈论树中的位置。任何时候我使用“最大值”我说的是节点的值或路径中节点值的总和。)

所以如果我们能找到它的最低深度节点,我们就知道最大值路径是由节点本身、一个由零个或多个节点组成的子路径(从它的左子节点(包括)下降)和一个子路径组成的。零个或多个节点从其右孩子下降(并包括)的路径。小步得出结论,左右下降路径必须是每边下降路径的最大值。 (如果这不明显,请考虑您选择的任何其他路径,您可以通过而不是选择该侧的最大值下降路径来增加总值。)如果这些路径中的一个或两个将有一个负值,那么我们就根本不包括负侧的任何节点。

所以我们有一个单独的子问题 - 给定一个子树,通过它的根下降的最大值路径的值是多少?好吧,它可能只是根本身,如果以它的孩子为根的所有路径的总和都是负数,或者它没有没有孩子。否则,它是根加上其子节点中的任何一个的最大值下降路径。这个子问题可以很容易地自行解决,但为了避免重复遍历和重做工作,我们会将它们组合成一次遍历树。

回到主要问题,我们知道some节点是最大值路径中深度最低的节点。我们甚至不特别关心知道何时访问它 - 我们只是递归地访问 每个 节点并找到以该路径作为其最低深度节点的最大值路径,确保在某个时候,我们将访问我们想要的那个。在每个节点,我们计算 both 从该点开始并在子树内下降的最大值路径 (max_single) 该节点最低的最大值路径- 路径中的深度节点 (max_top)。后者是通过获取节点并“粘合”零,通过其子节点的最大仅下降路径之一或两者来找到的。 (由于max_single 已经是从零或其中一个子节点下降的最大值路径,我们需要考虑的唯一额外的事情是通过两个子节点的路径。)通过在每个节点计算max_top 并保持最大在res.val 中找到的值,我们保证在完成遍历树时我们将找到所有值中的最大值。在每个节点上,我们返回 max_single 以用于父节点的计算。在算法结束时,我们只是从res.val 中提取答案。

【讨论】:

  • 小步得出结论,左右下降路径一定是每一边下降路径的最大值。 (如果这不明显,请考虑您选择的任何其他路径,您可以通过选择该侧的最大值下降路径来增加总值。)我不明白您的意思。您是说在左右子树中,选择具有最高值的一个将产生最大路径和?
  • 我是说我们正在查看整个图中具有最大值的路径,特别是在图中的最高节点处,位于左子树中的路径必须是从该点下降的最高值路径,位于右子树中的部分必须是从该点下降的最高值路径.
  • 图中最高节点是什么意思?您是指具有最大值的节点吗?我可能没有正确理解这一点,但是左右后代的总和怎么可能同时最高呢?
  • 哦,好的。因此,当您在第一条评论中声明 位于左子树中的路径部分必须是从该点下降的最高值路径,而位于右子树中的部分必须是从该点下降的最高值路径点。,你的意思是说在父节点左侧可用的所有路径中,我们应该选择总和最大的左侧路径(对于正确的)。我说的对吗?
  • 别担心,这不是比赛。 :D 只要我的回答和有用的东西我很高兴。
【解决方案2】:

特别是,当我们的变量 res.val 包含我们感兴趣的答案时,我不明白为什么在函数 findMaxUtil 中返回 max_single。

问题在于findMaxUtil() 确实做了两件事:它返回应用它的树的最大总和,并且它更新一个变量来跟踪迄今为止遇到的最大金额。原始代码中对此效果有评论,但您在问题中将其编辑了,也许是为了简洁:

// This function returns overall maximum path sum in 'res' 
// And returns max path sum going through root. 
int findMaxUtil(Node node, Res res) 

因为Java passes parameters by value, but every object variable in Java implicitly references the actual object,很容易忽略Resres参数中传递的事实可能会被这个函数改变。这正是您询问的行中发生的情况:

int max_single = Math.max(Math.max(l, r) + node.data, node.data);  

int max_top = Math.max(max_single, l + r + node.data); 

res.val = Math.max(res.val, max_top); 

return max_single;

第一行找到节点本身或节点加上最大子树的最大值,结果是max path sum going through root。在最后一行返回该值是该函数所做的 一个 事情。第二行和第三行查看该值并考虑它或包含两个孩子的路径是否大于任何先前看到的路径,如果是,则更新res,这是其他这个函数所做的事情。请记住,res 是存在于方法之外的某个对象,因此对它的更改会一直持续到递归停止并且启动整个事情的findMaxSum(Node) 返回res.val

所以,回到顶部的问题,findMaxUtil 返回max_single 的原因是它使用该值递归地确定通过每个子树的最大路径。 res 中的值更新,以便findMaxSum(Node) 可以使用它。

【讨论】:

  • 这个。感谢您在我开始之前进行解释......人们还可以注意到这可能不是编写函数的最佳方式。输出参数非常混乱。如果已经使用了Res 类,为什么不给它第二个属性,使对象不可变(public final int maxLengthInside; public final int maxLengthFromRoot;)并让每个调用返回一个这样的实例。
  • @Bergi 除了更容易理解之外,您的建议也适用于线程。问题中的实现至少需要锁定 res 以保证线程安全,这会减慢速度。
  • @Caleb ...findMaxUtil 返回max_single 的原因是它使用该值递归地确定通过每个子树的最大路径。 为什么应该函数返回max_single 而不是max_topl+r+node.data?它背后的直觉是什么?
  • @a_sid 查看它的调用位置并查看 cmets。调用者想知道树的任一侧的最大总和,而不是两者。 max_top 包括当前子树的两边;它可以是最大子路径总和,但不能是更高节点的子路径总和的一部分。
  • @Caleb 我明白了。我不明白它背后的原理。
【解决方案3】:

您缺少res.val 的值。该算法试图探索整棵树,使用等于到目前为止探索的最大路径长度的res.val。在每一步中,它会在子节点之间递归迭代,并使用最大路径长度更新res.val(如果大于已经存在的路径长度)。

证明:

假设您的算法适用于高度为n 的树。对于高度为n+1 的树,有一个根和两个高度为n 的子树。还要考虑findMaxUtili<=n 工作正常,并且会返回从子树的部分根开始的最大路径。

因此,树中高度为n+1 的最大路径计算如下

  1. findMaxUtil(subtree1)
  2. findMaxUtil(subtree2)
  3. findmaxUtil(subtree1)+root.data
  4. findmaxUtil(subtree2)+root.data
  5. findmaxUtil(subtree1)+findmaxUtil(subtree2)+root.data
  6. res.val

最后的结果是:findmaxUtil(newTree)=max(items 1:6)

【讨论】:

  • 为什么要执行步骤 3 和 4?它们似乎已经包含在 5 以内。我认为 6 实际上是你的最终结果?如:求subtree1 的最大值,然后是subtree2 的最大值,然后是它们两者之间的最大值和它们的共同根。
  • @Adriaan 最终结果是 1 到 6 中计算的六个值中的最大值。需要 3 和 4,因为它们中的任何一个都可能大于在 5 中计算的值。假设subtree1 给出 5,root.data 是 2,但 subtree2 是 -11。那么项目 1-6 是:{5, -11, 7, -9, -4, res.val},最大的是项目 3 或 res.val
猜你喜欢
  • 2020-11-29
  • 2018-08-10
  • 1970-01-01
  • 2021-07-24
  • 2016-03-17
  • 1970-01-01
  • 1970-01-01
  • 2016-09-07
  • 1970-01-01
相关资源
最近更新 更多