经典题:如何知道两棵二叉树相同?

169 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情


大家好呀,我是帅蛋。

今天解决相同的树,这是一道检验两棵二叉树是否相同的题目。

你如果看过我上篇关于【对称二叉树】的讲解,你就会发现在某种程度上这两道题可以看作是一道题。

那为什么我还要写这道题呢?

100-0

对,我就是要看看小婊贝儿们到底有没有学会。

100-1

LeetCode 100:相同的树

题意

给定两棵二叉树的根节点 p 和 q,编写一个函数来检验这两棵树是否相同。

如果两个树在结构上相同,并且节点具有相同的值,则认为他们是相同的。

示例

输入:p = [1,2,3],q = [1,2,3] 输出:true

100-2

提示

  • 两棵树上的节点数目都在范围 [0,100] 内。
  • -10^4 <= Node.val <= 10^4

题目解析

难度简单,似曾相识。

如果你已经看过【对称二叉树】这篇文章,那你看到这道题的时候,应该就已经知道怎么做了。

如果你没看过,那建议回过头儿去看一下,看完了,做这道题的感觉也就来了。

100-3

我在【对称二叉树】中说过,对称二叉树就是看左右子树是否互相翻转,和根节点没什么关系。

既然没什么关系,那把根节点拿掉,其实就是在比较两棵树。

100-4

那你看,从这个角度来看,这两道题其实就是一种类型的题,再直白点,就是一道题。

只不过稍微有点区别的是,对称二叉树是下面这种比较方式:

  • 左子树的左子树和右子树的右子树比较。
  • 左子树的右子树和右子树的左子树比较。

100-5

而本题相同的树则是下面这种比较方式:

  • p 树的根节点和 q 树的根节点比较。
  • p 树的左子树和 q 树的左子树比较。
  • p 树的右子树和 q 树的右子树比较。

100-6

是不是就很一目了然?

100-7

递归法

都是老套路。

100-8

根据【递归算法】文章中讲的,实现递归,需要两步:

  • 找出重复的子问题(递推公式)。
  • 终止条件。

(1) 找出重复的子问题。

这个在上面看图的时候其实已经找出来了。

p 树的左子树和 q 树的左子树,p 树的右子树和 q 树的右子树是否相等

如果相等就返回 true,不相等就返回 false。

# 判断两棵二叉树的左子树是否相同
leftTree = self.isSameTree(p.left, q.left)
# 判断两棵二叉树的右子树是否相同
rightTree = self.isSameTree(p.right, q.right)

(2) 确定终止条件。

同样,相同的树这道题因为比较的是节点的值是否相同,所以涉及的情况有点多。

首先是根节点为空的情况(空树) ,根节点为空,分为 3 种情况:

  • p,q 的根节点都为空,即为空树,显然两棵树相同。
  • p 的根节点为空,q 的根节点不为空,显然两棵树不相同。
  • p 的根节点不为空,q 的根节点为空,显然也是不相同的。

根节点为空的情况判断完了,剩下就是根节点不为空的情况,根节点不为空,其实终止就 1 种情况:

  • 比较 p,q 根节点的值,如果不相等,则两棵树不相同。

Python 代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
​
        # 若两棵二叉树皆为空树,则两棵二叉树相同
        if p == None and q == None:
            return True
        # 如果其中一棵二叉树为空,另一棵不为空,则一定不相同
        if (p == None and q != None) or (p != None and q == None):
            return False
        # 如果两棵二叉树皆不为空,但是根节点的值不同,则一定不相同
        if p.val != q.val:
            return False
        # 判断两棵二叉树的左子树是否相同
        leftTree = self.isSameTree(p.left, q.left)
        # 判断两棵二叉树的右子树是否相同
        rightTree = self.isSameTree(p.right, q.right)
​
        isSame = leftTree and rightTree
​
        return isSame

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 {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        // 若两棵二叉树皆为空树,则两棵二叉树相同
        if(p == null && q == null){
            return true;
        }
        // 如果其中一棵二叉树为空,另一棵不为空,则一定不相同
        if((p == null && q != null) || (p != null && q == null)){
            return false;
        }
        // 如果两棵二叉树皆不为空,但是根节点的值不同,则一定不相同
        if(p.val !=q.val){
            return false;
        }
        // 判断两棵二叉树的左子树是否相同
        boolean leftTree = isSameTree(p.left, q.left);
        //  判断两棵二叉树的右子树是否相同
        boolean rightTree = isSameTree(p.right,q.right);
​
        boolean isSame = leftTree && rightTree;
​
        return isSame;
    }
}

假设 p 树有 n 个节点,q 树有 m 个节点。

此解法,不管是 p 和 q 树是否相同,都会将节点少的树的每个节点遍历一遍,所以时间复杂度为 O(min(n,m))

使用递归,在过程中额外调用了栈空间,所以空间复杂度为 O(min(n,m))

非递归法(迭代)

这道题其实就是对于每一层来说,只要 p 树和 q 树的对应节点存在且相等即可

其实这还是类似于层次遍历,使用队列来解决

每次将 p 树和 q 树对应层的节点依次入队列,比如 p 的左节点和 q 的左节点入队列,p 的右节点和 q 的右节点入队列。

比如对于下图:

100-9

首先初始化队列,并将 p 树和 q 树的根节点入队列。

100-10

# 初始化队列
queue = [p, q]

当队列不为空,将前两个元素出队列进行比较。

100-11

# 从队列中取出两个节点
pNode = queue.pop(0)
qNode = queue.pop(0)
# 若当前节点为空,则继续循环
if pNode == None and qNode == None:
    continue
# 如果其中一个节点为空,另一个不为空,则一定不相同
if (pNode == None and qNode != None) or (pNode != None and qNode == None):
    return False
# 如果两个节点皆不为空,但是节点的值不同,则一定不相同
if pNode.val != qNode.val:
    return False

试下面再依次将 p 的左孩子和 q 的左孩子,p 的右孩子和 q 的右孩子入队列。

100-12

接下来还是按照上面的方式出队列、比较、入队列...直至队列为空。

Python 代码实现

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
        # 初始化队列
        queue = [p, q]
​
        while queue:
            # 从队列中取出两个节点
            pNode = queue.pop(0)
            qNode = queue.pop(0)
            # 若当前节点为空,则继续循环
            if pNode == None and qNode == None:
                continue
            # 如果其中一个节点为空,另一个不为空,则一定不相同
            if (pNode == None and qNode != None) or (pNode != None and qNode == None):
                return False
            # 如果两个节点皆不为空,但是节点的值不同,则一定不相同
            if pNode.val != qNode.val:
                return False
            # pNode 节点的左孩子和 qNode 节点的左孩子入队列
            queue.append(pNode.left)
            queue.append(qNode.left)
            # pNode 节点的右孩子和 qNode 节点的右孩子入队列
            queue.append(pNode.right)
            queue.append(qNode.right)
​
        return True

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 {
    public boolean isSameTree(TreeNode p, TreeNode q) {
        // 初始化队列
        LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
        queue.add(p);
        queue.add(q);
​
        while(queue.size() > 0) {
        // 从队列中取出两个节点
        TreeNode pNode = queue.removeFirst();
        TreeNode qNode = queue.removeFirst();
        // 若当前为空,则继续循环
        if(pNode == null && qNode == null) {
            continue;
        }
        // 如果其中一个节点为空,另一个不为空,则一定不相同
        if((pNode == null && qNode != null) || (pNode != null && qNode == null)){
            return false;
        }
        // 如果两个节点皆不为空,但是节点的值不同,则一定不相同
        if(pNode.val != qNode.val) {
            return false;
        }
        // pNode 节点的左孩子和 qNode 节点的左孩子入队列
        queue.add(pNode.left);
        queue.add(qNode.left);
        // pNode 节点的右孩子和 qNode 节点的右孩子入队列
        queue.add(pNode.right);
        queue.add(qNode.right);
        }
        return true;
    }
}

同样,非递归法,时间复杂度为 O(min(n,m)),空间复杂度为 O(min(n,m))


图解相同的树到这就结束辣,你写对了么?

通过这道题,我其实想说,很多时候你会发现,题目不过是在某类解决办法方面做加法减法

希望大家能多多思考,做题的时候不要做完了就算了。

当然文章也不能看完就算了呀,记得帮我点赞呀,么么哒。

我是帅蛋,我们下次见!