本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
Given the root
of a binary tree, find the maximum value v
for which there exist different nodes a
and b
where v = |a.val - b.val|
and a
is an ancestor of b
.
A node a
is an ancestor of b
if either: any child of a
is equal to b
or any child of a
is an ancestor of b
.
Example 1:
Input: root = [8,3,10,1,6,null,14,null,null,4,7,13]
Output: 7
Explanation: We have various ancestor-node differences, some of which are given below :
|8 - 3| = 5
|3 - 7| = 4
|8 - 1| = 7
|10 - 13| = 3
Among all possible differences, the maximum value of 7 is obtained by |8 - 1| = 7.
Input: root = [1,null,2,null,0,3]
Output: 3
Constraints:
[2, 5000]
.0 <= Node.val <= 105
题意:给定二叉树的根节点 root
,找出存在于 不同 节点 A
和 B
之间的最大值 V
,其中 V = |A.val - B.val|
,且 A
是 B
的祖先。如果 A 的任何子节点之一为 B,或者 A 的任何子节点是 B 的祖先,那么我们认为 A 是 B 的祖先。
如果节点 A A A 在从根节点到节点 B B B 的路径上,则称 A A A 是 B B B 的祖先节点,称 B B B 是 A A A 的子孙节点。
注:在这个定义中, B B B 的祖先节点可以是 B B B 自己。例如示例 1 中 6 6 6 的祖先节点自上而下依次为 8 , 3 , 6 8,3,6 8,3,6 。
注:虽然题目要求「不同节点」,但计算的是最大差值,相同节点算出来的差值为 0 0 0 ,不影响最大差值。
对于题目给出的公式 V = ∣ A . val − B . val ∣ V = |A.\textit{val} - B.\textit{val}| V=∣A.val−B.val∣ ,为了让 V V V 尽量大,分类讨论:
因此,无需记录递归路径中的全部节点值,只需要记录递归路径中的最小值 m n mn mn 和最大值 m x mx mx 。每递归到一个节点 B B B ,计算
m a x ( ∣ m n − B . v a l ∣ , ∣ m x − B . v a l ∣ ) max(∣mn−B.val∣,∣mx−B.val∣) max(∣mn−B.val∣,∣mx−B.val∣)
并更新答案的最大值。由于 mn ≤ B . val ≤ mx \textit{mn}\le B.\textit{val} \le \textit{mx} mn≤B.val≤mx ,上式可化简为 max ( B . val − mn , mx − B . val ) \max(B.\textit{val}-\textit{mn},\textit{mx}-B.\textit{val}) max(B.val−mn,mx−B.val)
class Solution {
private int ans;
public int maxAncestorDiff(TreeNode root) {
dfs(root, root.val, root.val);
return ans;
}
private void dfs(TreeNode node, int mn, int mx) {
if (node == null) return;
// 虽然题目要求「不同节点」,但相同节点的差值为 0,不会影响最大差值
// 所以先更新 mn 和 mx,再计算差值也可以
// 在这种情况下,一定满足 mn <= node.val <= mx
mn = Math.min(mn, node.val);
mx = Math.max(mx, node.val);
ans = Math.max(ans, Math.max(node.val - mn, mx - node.val));
dfs(node.left, mn, mx);
dfs(node.right, mn, mx);
}
}
换个角度看问题:对于一条从根出发向下的路径,我们要计算的实际上是这条路径上任意两点的最大差值。递归到叶子时, mx \textit{mx} mx 是从根到叶子的路径上的最大值, mn \textit{mn} mn 是从根到叶子的路径上的最小值,所以 mx − mn \textit{mx}-\textit{mn} mx−mn 就是从根到叶子的路径上任意两点的最大差值。所以无需每个节点都去更新答案,而是在递归到终点时才去更新答案。
class Solution {
private:
int ans = 0;
void dfs(TreeNode* r, int mx, int mn) {
if (r == nullptr) {
ans = max(mx - mn, ans);
return;
}
mx = max(mx, r->val);
mn = min(mn, r->val);
dfs(r->left, mx, mn);
dfs(r->right, mx, mn);
}
public:
int maxAncestorDiff(TreeNode* root) {
dfs(root, root->val, root->val);
return ans;
}
};
复杂度分析:
法一的思路是维护 B B B 的祖先节点中的最小值和最大值,我们还可以站在 A A A 的视角,维护 A A A 的子孙节点中的最小值 m n mn mn 和最大值 m x mx mx 。换句话说,最小值和最大值不再作为入参,而是作为返回值,意思是以 A A A 为根的子树中的最小值和最大值。
递归到节点 A A A 时,初始化 m n mn mn 和 m x mx mx 为 A . v a l A.val A.val ,然后递归左右子树,拿到左右子树的最小值和最大值,去更新 m n mn mn 和 m x mx mx ,然后计算
max ( ∣ mn − A . val ∣ , ∣ mx − A . val ∣ ) \max(|\textit{mn}-A.\textit{val}|,|\textit{mx}-A.\textit{val}|) max(∣mn−A.val∣,∣mx−A.val∣)
并更新答案的最大值。由于 mn ≤ A . val ≤ mx \textit{mn}\le A.\textit{val} \le \textit{mx} mn≤A.val≤mx ,上式可化简为
max ( A . val − mn , mx − A . val ) \max(A.\textit{val}-\textit{mn},\textit{mx}-A.\textit{val}) max(A.val−mn,mx−A.val)
class Solution {
int ans = 0;
pair<int, int> dfs(TreeNode *node) {
if (node == nullptr)
return {INT_MAX, INT_MIN}; // 保证空节点不影响 mn 和 mx
int mn = node->val, mx = mn;
auto [l_mn, l_mx] = dfs(node->left);
auto [r_mn, r_mx] = dfs(node->right);
mn = min(mn, min(l_mn, r_mn));
mx = max(mx, max(l_mx, r_mx));
ans = max(ans, max(node->val - mn, mx - node->val));
return {mn, mx};
}
public:
int maxAncestorDiff(TreeNode *root) {
dfs(root);
return ans;
}
};
复杂度: