其实题主本人看到这个题是有点懵的。
二叉树的直径?又是简单题,不就是左子树深度+右子树深度就行了?【在此,定义根节点深度为0】
写了半天,奇怪,计算最大深度我是会了,左右子树最大深度怎么算?
接着总算想起用两个变量存储左右子树深度,提交答案,怎么莫名出错?
随后注意到:树的直径不一定从根节点通过,这才大悟,可是更懵了,不从根节点通过,一个随机节点怎么找?该怎么解决?
后来也是看了题解,理解了一遍。
题目描述:
给你一棵二叉树的根节点,返回该树的 直径 。
二叉树的 直径 是指树中任意两个节点之间最长路径的 长度 。这条路径可能经过也可能不经过根节点 root
。
两节点之间路径的 长度 由它们之间边数表示。
示例 1:
输入:root = [1,2,3,4,5] 输出:3 解释:3 ,取路径 [4,2,1,3] 或 [5,2,1,3] 的长度。
示例 2:
输入:root = [1,2] 输出:1
提示:
- 树中节点数目在范围
[1, 104]
内 -100 <= Node.val <= 100
解题准备:
1.了解二叉树:二叉树由根节点、左子树、右子树组成。
2.理解题意:题目求二叉树直径,如果是随机节点,编码起来必然千难万难,既需要考虑节点root,又得考虑root.left,root.right等等,如果得到了经过root节点的直径,又得拿左右子节点的直径,此时问题非常复杂,且难以划分。
解题难点1分析:
正如上述:随机节点难以解决,因此需要将其转化为“非随机”节点。
从系统的角度分析,我们希望输入、处理流程可以固定。
化简:假设直径必然经过根节点
如题,如果直径必然经过根节点,该如何处理?
由于经过根节点,所以假设根节点与左子树的最大距离是L,与右子树是R,那么,简化后,直径是L+R。
重点就转化为:求左子树、右子树的最大深度。
再化简,转为求树root的最大深度:root的最大深度,就是左子树最大深度(或者右子树最大深度)+1,如此递归下去,直到得到结果。
伪代码为:
// 伪代码
public int maxDepth(TreeNode root){
if(root==null){
return 0; // 空树的最大深度为0
}
int lD=maxDepth(root.left); // 左子树最大深度
int rD=maxDepth(root.right); // 右子树最大深度
return Math.max(lD, rD)+1; // 返回最大深度
}
当然,求左子树、右子树的最大深度,与单独求根节点的最大深度还是有区别,如果我们确信直径一定经过根节点,那么将root.left、root.right传递过去,分别得到左右子树的深度,然后相加即可,伪代码为:
// 伪代码
public int solution(TreeNode root){
int lD=maxDepth(root.left); // maxDepth即前一段代码定义的求最大深度函数
int rD=maxDepth(node.right); //
return lD+rD;
}
至此,解题难点1分析的化简题已经完成,需要重新考虑问题。
我们知道,直径不一定经过根节点。
解题难点2分析:
我们有手段得到二叉树的遍历,即使用递归或迭代的方式,如果我们借助TreeNode数组,保存所有节点。
然后,计算每个节点的直径【也就是,对于每个节点,计算经过该节点的直径】
最后,进行判断,得到最大直径,就可以解决问题。
得到遍历序列,解决直径问题:
已经有根节点作为示例,在此不再讲解,代码展示如下:
class Solution {
public int diameterOfBinaryTree(TreeNode root) {
List<TreeNode> list=new ArrayList<TreeNode>();
int res=0;
getData(list, root); // 得到中序遍历序列(哪个序都一样)
// 遍历节点
for(TreeNode data:list){
int lD=maxDepth(data.left);
int rD=maxDepth(data.right);
res=Math.max(res, lD+rD); // 比较节点的直径和目前最大直径
}
return res;
}
public void getData(List<TreeNode> list, TreeNode root){
if(root==null){
return;
}
getData(list, root.left);
list.add(root);
getData(list, root.right);
}
private int maxDepth(TreeNode node){
if(node==null){
return 0;
}
int lD=maxDepth(node.left);
int rD=maxDepth(node.right);
return Math.max(lD, rD)+1;
}
}
目前,已经真正解决了该问题,不过时间复杂度比较高,首先是中序遍历,大概在O(n),然后是迭代,迭代中嵌套计算直径的函数,大概在O(n^2),因此总体大约达到O(n^2);
解题难点3分析:
优化:这道题目,使用这种算法,会有很多数据被重复计算,比如下图(示例图):
在计算节点1的直径时,已经计算到3的节点不可能是直径(因为它是叶子节点),还有4、5也不可能是直径,最多计算1、2,就可以返回答案。
优化:如何避免大量重复运算?
既然已经知道经由某个节点的直径,我们是否可以从该方法入手改造?
我们知道,maxDepth函数的作用,是通过比较左右子树的深度,得到最大深度。
同时,我们又需要左右子树的深度,来辅助计算直径【同样看成通过某个节点的直径】
那么,可以直接改造吧?
对于节点root,得到其左右深度lD、rD,使lD+rD,比较lD+rD与原直径,哪个大哪个就是新直径。【这道题的子问题,其实是求深度,因此原直径不能在函数体中定义,而是定义全局变量】
代码:
class Solution {
int ans;
public int diameterOfBinaryTree(TreeNode root) {
ans = 0;
depth(root);
return ans;
}
public int depth(TreeNode node) {
if (node == null) {
return 0; // 访问到空节点了,返回0
}
int L = depth(node.left); // 左儿子为根的子树的深度
int R = depth(node.right); // 右儿子为根的子树的深度
ans = Math.max(ans, L+R); // 更新直径。
return Math.max(L, R) + 1; // 返回该节点为根的子树深度
}
}
以上内容即我想分享的关于力扣热题16
的一些知识。
我是蚊子码农,如有补充,欢迎在评论区留言。个人也是初学者,知识体系可能没有那么完善,希望各位多多指正,谢谢大家。