Problem: 1339. 分裂二叉树的最大乘积
文章目录
- 整体思路
-
-
- [1. 核心问题与功能](#1. 核心问题与功能)
- [2. 算法与逻辑步骤](#2. 算法与逻辑步骤)
-
- 完整代码
- 时空复杂度
-
-
- [1. 时间复杂度: O ( N ) O(N) O(N)](#1. 时间复杂度: O ( N ) O(N) O(N))
- [2. 空间复杂度: O ( N ) O(N) O(N)](#2. 空间复杂度: O ( N ) O(N) O(N))
-
整体思路
1. 核心问题与功能
这段代码的核心目的是:在二叉树中删除一条边,将树分裂成两个子树,使得这两个子树的元素和的乘积最大 。
最终返回这个最大乘积对 10 9 + 7 10^9 + 7 109+7 取模后的结果。
2. 算法与逻辑步骤
该算法采用了两次深度优先搜索 (DFS) 的策略:
-
第一次遍历 (
dfs1) - 计算总和:- 我们需要知道整棵树所有节点的数值总和(记为
TotalSum)。 - 通过一次后序遍历(递归),计算出整棵树的和。
- 我们需要知道整棵树所有节点的数值总和(记为
-
第二次遍历 (
dfs2) - 寻找最大乘积:- 我们需要模拟删除每一条边的过程。
- 实际上,删除某个节点与其父节点之间的边,就会形成一个以该节点为根的子树。
- 如果在该节点处断开,两个子树的和分别为:
- 当前子树和:
SubTreeSum - 剩余部分和:
TotalSum - SubTreeSum
- 当前子树和:
- 我们在第二次 DFS 过程中,再次计算每个节点的子树和。每计算出一个子树和,就立即计算当前的乘积
SubTreeSum * (TotalSum - SubTreeSum),并更新全局最大值ans。
-
数据结构与细节:
ans使用long类型,因为两个子树和的乘积很容易超过int的范围。MOD用于最后的取模操作。
完整代码
java
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
// 定义模数常量 10^9 + 7
private static final int MOD = 1_000_000_007;
// 使用 long 类型存储最大乘积,防止中间计算过程溢出
private long ans = 0;
// 第一次 DFS:主要目的是计算整棵树的总和
// 返回值是当前子树的节点和
private int dfs1(TreeNode node) {
if (node == null) {
return 0;
}
// 后序遍历:当前节点值 + 左子树和 + 右子树和
return node.val + dfs1(node.left) + dfs1(node.right);
}
// 第二次 DFS:计算子树和,并利用传入的总和 sum 计算乘积,更新最大值
// node: 当前节点
// sum: 整棵树的总和(由 dfs1 计算得出,作为常量传入)
private int dfs2(TreeNode node, int sum) {
if (node == null) {
return 0;
}
// 递归计算当前子树的和 (curSum)
// 逻辑与 dfs1 相同,但在过程中增加了更新 ans 的步骤
int curSum = node.val + dfs2(node.left, sum) + dfs2(node.right, sum);
// 核心逻辑:
// curSum 是以当前 node 为根的子树和。
// sum - curSum 是移除该子树后,剩余部分树的和。
// 两者相乘即为切断 node 与其父节点连接后的乘积。
// 使用 (long) 强制转换防止乘法溢出 int 范围。
ans = Math.max(ans, (long) curSum * (sum - curSum));
// 返回当前子树和,供父节点计算使用
return curSum;
}
public int maxProduct(TreeNode root) {
// 第一步:通过 dfs1 获取整棵树的所有节点值之和
int sum = dfs1(root);
// 第二步:通过 dfs2 遍历每个子树,寻找分裂后的最大乘积
// 注意:dfs2 的返回值(整棵树的和)在这里没用到,我们只关心过程中对 ans 的更新
dfs2(root, sum);
// 第三步:将结果对 10^9 + 7 取模并转换为 int 返回
return (int) (ans % MOD);
}
}
时空复杂度
1. 时间复杂度: O ( N ) O(N) O(N)
- 计算依据 :
- 代码执行了两次 DFS 遍历 (
dfs1和dfs2)。 - 在每次遍历中,每个节点都被访问一次。
- 每个节点内部的操作(加法、乘法、比较大小)都是 O ( 1 ) O(1) O(1) 的常数时间操作。
- 总时间 = O ( N ) + O ( N ) = 2 × O ( N ) O(N) + O(N) = 2 \times O(N) O(N)+O(N)=2×O(N)。
- 代码执行了两次 DFS 遍历 (
- 结论 : O ( N ) O(N) O(N),其中 N N N 是二叉树的节点总数。
2. 空间复杂度: O ( N ) O(N) O(N)
- 计算依据 :
- 该算法没有显式地使用额外的数组或列表来存储数据(只使用了几个变量)。
- 主要的空间消耗来自递归调用栈。
- 递归的深度取决于二叉树的高度 H H H。
- 最坏情况下(树退化为链表),高度 H = N H = N H=N,空间复杂度为 O ( N ) O(N) O(N)。
- 平均/最好情况下(完全二叉树),高度 H = log N H = \log N H=logN,空间复杂度为 O ( log N ) O(\log N) O(logN)。
- 结论 : O ( N ) O(N) O(N)(基于最坏情况的大 O 表示法)。
参考灵神