匹配类二叉树可以使用一种套路相对固定的递归函数,在周赛中和每日一题中多次出现,而第一次见到不太容易写出正确的递归解法,因此我们来总结一下。(注:不要太纠结于名字,因为名字是我自己起的......)
这类题目与字符串匹配有些神似,求解过程大致分为两步:
- 先将根节点匹配;
- 根节点匹配后,对子树进行匹配。
而参与匹配的二叉树可以是一棵,与自身匹配;也可以是两棵,即互相匹配。
比如每日一题的「101. 对称二叉树」就是两棵树之间的匹配问题。为了更具一般性,我们先来看「面试题 04.10. 检查子树」这道题。
面试题 04.10. 检查子树
这道题的题意是这样的:输入两棵二叉树 A
和 B
,判断 B
是不是 A
的子结构,且约定空树不是任意一个树的子结构。
比如上面这个例子,我们发现 B
是 A
的子结构,因为它们的结构相同,且节点值相等。
求解思路可以分解为以下两步:
- 匹配根节点:首先在
A
中找到与B
的根节点匹配的节点C
; - 匹配其他节点:验证
C
的子树与B
的子树是否匹配。
最终代码如下:
代码中主要涉及 主函数和 dfs 函数 两个部分。与以上思路对应,主函数对应根节点的匹配,dfs 函数对应匹配其他节点。
dfs 函数
dfs 函数将注意力集中在了根节点已经匹配的情况。当从根节点同时开始向下遍历时,我们进行以下判断:
- 如果
A
和B
同时遍历到了null
,说明匹配成功,返回True
(case 1 红色虚线框); - 如果
A
或B
提前遍历到了null
,一棵树匹配完了,另一棵却没有,说明子树的结构是不同的,则匹配失败,返回False
(case 2 红色虚线框)。
以上是“基本情况”,在到达基本情况之前,我们需要判断根节点的值、左子树(调用递归)和右子树(调用递归)是否匹配。
于是就有了代码中的 dfs 函数形式:
主函数
现在将视野放远来看,主函数则解决了如何确定 A 的哪个节点是 B 的根节点。
如果 A
的当前节点值与 B
的根节点值相同,我们调用 dfs 函数判断子树是否也相同;如果不同,我们就递归调用主函数来寻找 A
的哪个节点与 B
的根节点匹配。
所以,主函数会写成这样:
当然,主函数还经常需要判断以下边界条件,比如如果 A
为空,则肯定不匹配,返回 False
。
总结
熟悉了以上的思路之后,力扣很多类似的题目都可以使用题目中的代码解决。
这些题目中,可能会有自身和自身做匹配的,比如每日一题「101. 对称二叉树」,将自身看作两棵树,用左子树和右子树镜像比较;
可能会将另一棵树变成一个链表,比如「1367. 二叉树中的列表」,仍然是先将链表头部与二叉树的某个节点匹配,再验证后续是否匹配;
也可能像「面试题26. 树的子结构」这样,前面的例题很像,不同的是 B
属于 A
的一部分也可以,没必要一直匹配到叶子节点;
还有与例题相同思路的「572. 另一个树的子树」
......
算法与技术面试zhuanlan.zhihu.com