Skip to content

Commit 2a31011

Browse files
committed
289
1 parent edb9fcc commit 2a31011

File tree

2 files changed

+126
-1
lines changed

2 files changed

+126
-1
lines changed

readme.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
前端界的好文精读,每周更新!
88

9-
最新精读:<a href="./算法/288.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%E9%A2%98%20-%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%E3%80%8B.md">288.精读《算法题 - 编辑距离》</a>
9+
最新精读:<a href="./算法/289.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%E9%A2%98%20-%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C%E3%80%8B.md">289.精读《算法题 - 二叉树中的最大路径和》</a>
1010

1111
素材来源:[周刊参考池](https://github.com/ascoders/weekly/issues/2)
1212

@@ -307,6 +307,7 @@
307307
- <a href="./算法/285.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%E9%A2%98%20-%20%E6%9C%80%E5%B0%8F%E8%A6%86%E7%9B%96%E5%AD%90%E4%B8%B2%E3%80%8B.md">285.精读《算法题 - 最小覆盖子串》</a>
308308
- <a href="./算法/286.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%E9%A2%98%20-%20%E5%9C%B0%E4%B8%8B%E5%9F%8E%E6%B8%B8%E6%88%8F%E3%80%8B.md">286.精读《算法题 - 地下城游戏》</a>
309309
- <a href="./算法/288.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%E9%A2%98%20-%20%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%E3%80%8B.md">288.精读《算法题 - 编辑距离》</a>
310+
- <a href="./算法/289.%E7%B2%BE%E8%AF%BB%E3%80%8A%E7%AE%97%E6%B3%95%E9%A2%98%20-%20%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E7%9A%84%E6%9C%80%E5%A4%A7%E8%B7%AF%E5%BE%84%E5%92%8C%E3%80%8B.md">289.精读《算法题 - 二叉树中的最大路径和》</a>
310311

311312
### 可视化搭建
312313

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
今天我们看一道 leetcode hard 难度题目:[二叉树中的最大路径和](https://leetcode.cn/problems/binary-tree-maximum-path-sum/description/)
2+
3+
## 题目
4+
5+
二叉树中的 路径 被定义为一条节点序列,序列中每对相邻节点之间都存在一条边。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
6+
7+
路径和 是路径中各节点值的总和。
8+
9+
给你一个二叉树的根节点 `root` ,返回其 最大路径和 。
10+
11+
示例1:
12+
13+
```
14+
输入:root = [1,2,3]
15+
输出:6
16+
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
17+
```
18+
19+
## 思考
20+
21+
第一想法是,这道题不安常理出牌,因为路径竟然不是自上而下的,而是可以横向蛇形游走的,如下图:
22+
23+
<img width=400 src="https://user-images.githubusercontent.com/7970947/280512728-e5b0c656-1a01-4961-bcb0-aa6b5f8718d2.png">
24+
25+
## 尝试动态规划
26+
27+
第二想法是,这种蛇形游走的路径,求路径最大值应该用什么方法?大概率是暴力解法,因为 **必须遍历完所有节点,才知道是否有更大的值的可能性**,而应对暴力解法最好的策略是动态规划,那么应该如何定义状态?经过一番思考,二叉树点到点之间仅有唯一一条路径,如果我们能枚举计算经过每个点的所有可能路径的最大值,那么找到其中最大的就可以得到答案。但可惜的是,以 “点” 为变量没办法写转移方程。
28+
29+
## 以暴力解法为基础思考
30+
31+
此时要切换想法,经过一些思考,我决定以正序角度模拟一下寻找最大路径和的思路:首先选择一个起点,找到以该起点开始的最大路径合。那么从该起点就有最多 3 种走法,分别是向根节点走、左子节点、右子节点走:
32+
33+
<img width=400 src="https://user-images.githubusercontent.com/7970947/280513043-49c59185-a482-48d8-972a-5a35def5df7f.png">
34+
35+
**最暴力的解法是遍历每个点,把所有方向都走一遍,找到所有可能的最大值。** 这无疑是一个最有效的兜底解法,但效率太低,那么为了提升效率,假设一条路径的最大潜力已经计算过一次了,那么一条新路径经过时,就没必要重新算一遍。**所以我们要寻找每个方向的最大贡献**
36+
37+
## 寻找每个方向的最大贡献
38+
39+
假设我们提前找到了经过每个点的最大贡献如下:
40+
41+
<img width=400 src="https://user-images.githubusercontent.com/7970947/280513172-bd8531bd-6f92-4e2e-8f9d-9ec726876a62.png">
42+
43+
根节点的最大贡献 10 的含义为:从 3 向根节点走,所有可能路径能带来的最大正数收益为 10。所以此时最大路径和显然为:5 + 3 + 10 = 18.
44+
45+
但此时矛盾来了,根节点的最大贡献 10 是从 3 向根节点走的角度定义的,它有两个致命问题:
46+
47+
1. 每个节点的最大贡献最好只能有一个数字,依赖方向的话复杂度太高了。
48+
2. 如果要依赖方向,那么从根节点右子节点走向根节点的最大贡献,其实依赖从左子结点出发的最大贡献,相互死锁了。
49+
50+
这种最大贡献几乎不可能找到,再花时间思考只是浪费时间,所以我们要改变策略了。再想想二叉树的特征是什么,怎么样能最稳定的定义每个节点的最大贡献?很容易想到的是以树的深度来定义,即 **以当前节点向子节点遍历时,能带来的最大贡献**。这种最大贡献是比较容易计算的。
51+
52+
## 每个子树的最大贡献
53+
54+
<img width=400 src="https://user-images.githubusercontent.com/7970947/280513452-9ff78c27-9c71-4d4c-816b-6a1c5f7eae5e.png">
55+
56+
如上图所示,以 8 这个节点的子树,假设通过一系列递归找到,它能提供的最大贡献就是 8,**且这个贡献必须是一条没有分叉的线**,这样这个最大贡献对于它的父节点才有意义,即父节点可以把这个节点连上,形成一条更长的没有分叉的线。如果子线都有分叉,整条线就会存在分叉,就不符合题意了。
57+
58+
这个 8 很容易计算,从叶子结点向上推,找到最大且大于 0 的子节点连成线即可。
59+
60+
但回到这道题,如果我们仅仅计算了每个点所在子树的最大贡献,那么其最大值仅是垂直的线中的最大值,没有考虑到该题路径可以横向蛇形游走的特性:
61+
62+
<img width=400 src="https://user-images.githubusercontent.com/7970947/280513643-9fed61c0-0900-485c-87e3-82a7b2cc6f3d.png">
63+
64+
如上图所示,红色的数字为以该点开始的子树的最大贡献,那么根节点 32 其实就是红色路径提供的路径和,对于纵向走位来说是最大的,但并不是本题最大的。本题最大的值,还得把下图红色的路径考虑上,变成一个横向的线,此时最大值达到了 32 + 8 = 40:
65+
66+
<img width=400 src="https://user-images.githubusercontent.com/7970947/280513696-62fb6f05-e87b-45cf-a079-2ca779bea8d9.png">
67+
68+
**但其实要把线变成横向的,也仅需要多考虑另一个子节点而已**,因为所有子树的最大贡献已经提前算好,根本无需再深入子子节点。也就是说,在计算最大路径和时(重要内容字体加粗!):
69+
70+
1. 经过该点的最大路径和,要同时考虑该点 + 左右子树最大贡献,也就是此时路径会形成类似倒扣的 U 型。
71+
2. 但该节点的最大贡献呢,只能考虑该点 + 左 or 右子树最大贡献的,不能形成倒扣的 U 型,因为这个最大贡献需要被其父节点作第 1 条规则时考虑,如果此时已经是倒扣 U 型了,那么父节点再分叉一次倒扣的 U 型,就不是一条线了,可能会形成如下图所示奇怪的形状:
72+
73+
<img width=400 src="https://user-images.githubusercontent.com/7970947/280513879-c46e3635-4a43-40da-9171-95138ca70131.png">
74+
75+
这就是本题最精彩的思考点。
76+
77+
## 代码实现
78+
79+
想通了之后,代码就很简单了:
80+
81+
```ts
82+
function maxPathSum(root: TreeNode | null): number {
83+
let maxValue = -Infinity
84+
85+
function maxOneLinePathByNode(node: TreeNode): number {
86+
// 如果节点为空,返回负无穷,必然不会被最大路径和带上
87+
if (node === null) return -Infinity
88+
89+
// 左子树最大贡献(如果为负数则为 0,表示不带上左子树)
90+
const leftChildMaxValue = Math.max(maxOneLinePathByNode(node.left), 0)
91+
// 右子树最大贡献 - 同理
92+
const rightChildMaxValue = Math.max(maxOneLinePathByNode(node.right), 0)
93+
94+
// 经过该点的最大路径和
95+
const currentPointMaxValue = node.val + leftChildMaxValue + rightChildMaxValue
96+
// 刷新 maxValue
97+
maxValue = Math.max(maxValue, currentPointMaxValue)
98+
99+
// 返回不分叉的子树最大贡献
100+
return node.val + Math.max(leftChildMaxValue, rightChildMaxValue)
101+
}
102+
103+
maxOneLinePathByNode(root)
104+
105+
return maxValue
106+
};
107+
```
108+
109+
因为从根节点开始递归,可以算出所有子树的最大贡献,**把经过每一个点的路径都考虑到了**,所以答案是不重不漏的。
110+
111+
## 总结
112+
113+
该题有两个难点:
114+
115+
1. 找到子树最大贡献思考方向。
116+
2. 子树最大贡献与最大路径和的计算方式稍有不同,需要分别处理。
117+
118+
最后,在从根节点递归寻找子树最大贡献时,就可以顺便计算出最大路径和,一定程度上是 “目标的副产物”,甚至可以怀疑该题是在思考子树最大贡献时,逆向推导出来的副产物。另一方面,也说明了子树最大贡献的重要性,它的一个衍生计算就可以是一道 hard 题。
119+
120+
> 讨论地址是:[精读《算法 - 二叉树中的最大路径和》· Issue #504 · dt-fe/weekly](https://github.com/dt-fe/weekly/issues/505)
121+
122+
**如果你想参与讨论,请 [点击这里](https://github.com/dt-fe/weekly),每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。**
123+
124+
> 版权声明:自由转载-非商用-非衍生-保持署名([创意共享 3.0 许可证](https://creativecommons.org/licenses/by-nc-nd/3.0/deed.zh)

0 commit comments

Comments
 (0)