某大厂员工吐槽:直接用git pull拉代码,被diss!
一个大厂员工说,自己平时拉代码一直顺手 git pull,结果干活干着干着,大群突然有人点名他,说你这样不行,会把提交历史搞得一团糟,后面排查问题都费劲。
他人都懵了。
不过也能理解为啥有人急。团队协作里,有些人特别在意提交线要干净,能 rebase 就别 merge,看记录像看地铁线路图一样,最好一条直线到底。
但问题是,公司里很多规范真不是写在门口的。没人讲,没人培训,默认大家都懂。等你踩到了,才在大群里补一刀。
程序员老鬼只能说,技术债有时候不是代码写出来的,是沟通省出来的。下次别光 diss,顺手把规范文档甩出来,大家都省心。
算法题:二叉树的直径
二叉树的直径,别上来就数节点
这题坑不在代码长,坑在“直径”这两个字看着太像根节点两边相加。
我第一次看这种题,第一反应也会往根节点上凑:左子树高度 + 右子树高度,不就完了么?
不一定。
直径可能经过根节点,也可能压根不经过根节点。比如某个左子树里面自己长得很歪,它内部两条链加起来,比经过整棵树根节点还长。
所以这题不能只在根节点算一次,得在每个节点都算一次。
我一般这么判断:
对任意一个节点来说,经过它的最长路径就是:
左子树高度 + 右子树高度
然后全局取最大值。
这里要注意,很多题里的直径指的是“边数”,不是“节点数”。所以空节点高度返回 0,叶子节点高度返回 1,这样左右高度相加,刚好就是边数。
代码可以写得很短,不需要搞一堆花活。
classSolution{
privateint maxPath = 0;
publicintdiameterOfBinaryTree(TreeNode root){
calcHeight(root);
return maxPath;
}
privateintcalcHeight(TreeNode node){
if (node == null) {
return0;
}
int leftHeight = calcHeight(node.left);
int rightHeight = calcHeight(node.right);
int passThisNode = leftHeight + rightHeight;
if (passThisNode > maxPath) {
maxPath = passThisNode;
}
return Math.max(leftHeight, rightHeight) + 1;
}
}
树节点大概长这样:
classTreeNode{
int val;
TreeNode left;
TreeNode right;
TreeNode(int val) {
this.val = val;
}
}
这段代码里有两个东西别混了。
calcHeight 返回的是“当前节点往下能走多深”。
maxPath 记录的是“目前见过的最长直径”。
比如走到某个节点,它左边高度是 3,右边高度是 2,那经过这个节点的路径长度就是 5。这个 5 不一定就是最终答案,但要拿出来跟全局最大值比一下。
然后函数返回给父节点的时候,不能返回 3 + 2。
因为父节点只能选你这棵子树里的一条链继续往上接,不可能左边右边都要。这里是很多人写错的地方。
所以返回值只能是:
Math.max(leftHeight, rightHeight) + 1
看一个简单的树:
1
/ \
2 3
/ \
4 5
在节点 2 上,左高度是 1,右高度是 1,经过它的路径是 2。
在节点 1 上,左高度是 2,右高度是 1,经过它的路径是 3。
最后答案就是 3,对应路径可以是:
4 -> 2 -> 1 -> 3
这里数的是边,不是节点。节点有 4 个,边是 3 条。
复杂度也没什么悬念,每个节点只访问一次,时间复杂度 O(n)。递归栈和树高有关,最坏链表形状是 O(n),平衡一点就是 O(log n)。
这题写到最后,其实就一句话:后序遍历算高度,顺手更新每个节点作为拐点时的最大路径。
别只盯着根节点,根节点经常只是个路过的。