程序员老鬼

一个被裁员的45岁中年男人的,大儿子16岁,高一,小儿子9岁3年级房贷122万,月供11400元

刚刷到这个帖子,真有点闷。

一个45岁的中年男人,原来年收入还可以,天天加班,家里两个娃,一个高中,一个小学,房贷还背着一百多万,每个月一万多的月供。结果去年夏天公司一刀裁下来,赔了十几万,看着不少,其实放到这种家庭里,根本撑不了多久。

Image

更难受的是,他后面找了半年工作,没找到。人一下从“家里主要收入来源”,变成天天买菜做饭、收拾家务,在家说话都小心。烟酒戒了,一天少吃一顿,已经不是省钱了,是整个人开始往回缩。

最扎心的是老婆那边也顶着压力,脸色不好,话也难听。可你说这男的懒吗?也不是。他只是被职场这辆车甩下来了。

45岁失业,最狠的不是没工作,是你突然发现,家里每一笔开销都在等你解释。

今日面试题

root 这棵树扫到某个节点时,突然发现它下面那一坨结构,和 subRoot 长得一模一样。

这题就干这一件事。

别一上来就想什么序列化、KMP、树哈希。那些能做,但面试里先把递归写稳,比整花活更重要。树题最怕的不是思路难,是边界条件一乱,递归直接开始胡说。

题目叫“另一棵树的子树”,判断的是:subRoot 是不是 root 的某个子树。

注意,是子树,不是包含几个相同节点就行。

比如:

root:
        3
       / \
      4   5
     / \
    1   2

subRoot:
      4
     / \
    1   2

这个是 true。

但如果 root 里 4 下面多了一个节点,那就不是了。结构和值都要完全一致。

我一般会拆成两个判断。

一个负责在 root 里找起点。

一个负责从某个起点开始,判断两棵树是不是完全一样。

代码就这么写:

classSolution{

publicbooleanisSubtree(TreeNode root, TreeNode subRoot){
if (subRoot == null) {
returntrue;
        }
if (root == null) {
returnfalse;
        }

if (sameTree(root, subRoot)) {
returntrue;
        }

return isSubtree(root.left, subRoot)
                || isSubtree(root.right, subRoot);
    }

privatebooleansameTree(TreeNode a, TreeNode b){
if (a == null && b == null) {
returntrue;
        }
if (a == null || b == null) {
returnfalse;
        }
if (a.val != b.val) {
returnfalse;
        }

return sameTree(a.left, b.left)
                && sameTree(a.right, b.right);
    }
}

这里有个地方别写反了。

isSubtree 是“找入口”,所以 root 为空的时候,只能说明没地方找了,返回 false。

sameTree 是“对齐比较”,所以两个节点同时为空,才算这一段匹配成功。

这两个递归的语义不一样,很多人代码错就错在这里。看起来都是判空,实际上不是一回事。

再看一眼执行过程。

假设 root 当前节点是 3,subRoot 根节点是 4。

sameTree(3, 4)

值不一样,直接 false。

然后继续往左找:

isSubtree(root.left, subRoot)

这时 root.left 是 4,再比较:

sameTree(4, 4)

根节点值一样,就继续比较左子树、右子树。只要有一个地方结构不一样,或者值不一样,就会返回 false。

这个写法不快到离谱,但很稳。

时间复杂度最坏是 O(m * n)。因为 root 的每个节点都可能拿来和 subRoot 比一遍。

不过普通面试题里,这个解法完全够用。除非题目数据特别大,或者明确要求优化,再考虑把树序列化成字符串,或者用树哈希。那是另一套排查思路,不是这题第一版该干的事。

树题我一直有个习惯:先把函数职责写窄。

isSubtree 不负责细比。

sameTree 不负责到处找。

职责一混,递归就会变成一团。看着少写了几行,调试的时候全还回来。