LeetCode第100题:相同的树

85 阅读8分钟

LeetCode第100题:相同的树

题目描述

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

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

难度

简单

问题链接

leetcode.cn/problems/sa…

示例

示例 1:

示例1

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

示例 2:

示例2

输入:p = [1,2], q = [1,null,2]
输出:false

示例 3:

示例3

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

提示

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

解题思路

判断两棵二叉树是否相同是一个经典的递归问题。我们可以通过比较两棵树的结构和节点值来确定它们是否相同。

方法一:递归

递归是解决树问题的常用方法。对于判断两棵树是否相同,我们可以使用以下递归策略:

  1. 如果两个节点都为空,则它们相同。
  2. 如果其中一个节点为空而另一个不为空,则它们不相同。
  3. 如果两个节点的值不相等,则它们不相同。
  4. 递归地比较左子树和右子树。

方法二:迭代(使用队列或栈)

我们也可以使用迭代的方式来解决这个问题,通过使用队列或栈来模拟递归过程:

  1. 将两棵树的根节点分别入队(或入栈)。
  2. 当队列(或栈)不为空时,取出两个节点进行比较。
  3. 如果两个节点都为空,继续下一轮比较。
  4. 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回 false
  5. 将两个节点的左子节点和右子节点分别入队(或入栈)。
  6. 如果队列(或栈)为空,则返回 true

算法步骤分析

递归方法:

  1. 如果 pq 都为 null,返回 true
  2. 如果 pnullq 不为 null,或者 p 不为 nullqnull,返回 false
  3. 如果 p.val 不等于 q.val,返回 false
  4. 递归地比较 p.leftq.left,以及 p.rightq.right
  5. 如果左子树和右子树都相同,返回 true,否则返回 false

迭代方法(使用队列):

  1. 创建一个队列,将 pq 入队。
  2. 当队列不为空时:
    • 取出队首的两个节点 node1node2
    • 如果 node1node2 都为 null,继续下一轮循环。
    • 如果 node1nullnode2 不为 null,或者 node1 不为 nullnode2null,返回 false
    • 如果 node1.val 不等于 node2.val,返回 false
    • node1.leftnode2.left 入队。
    • node1.rightnode2.right 入队。
  3. 如果队列为空,返回 true

算法可视化

以示例 1 为例,p = [1,2,3], q = [1,2,3]

递归方法:

  1. 比较根节点:p.val = 1, q.val = 1,相等。
  2. 递归比较左子树:p.left.val = 2, q.left.val = 2,相等。
    • p.left.left = null, q.left.left = null,相等。
    • p.left.right = null, q.left.right = null,相等。
  3. 递归比较右子树:p.right.val = 3, q.right.val = 3,相等。
    • p.right.left = null, q.right.left = null,相等。
    • p.right.right = null, q.right.right = null,相等。
  4. 所有比较都相等,返回 true

迭代方法(使用队列):

  1. pq 入队:队列 = [p, q]。
  2. 取出 pqp.val = 1, q.val = 1,相等。
  3. p.leftq.left 入队:队列 = [p.left, q.left]。
  4. p.rightq.right 入队:队列 = [p.left, q.left, p.right, q.right]。
  5. 取出 p.leftq.leftp.left.val = 2, q.left.val = 2,相等。
  6. p.left.leftq.left.left 入队:队列 = [p.right, q.right, null, null]。
  7. p.left.rightq.left.right 入队:队列 = [p.right, q.right, null, null, null, null]。
  8. 取出 p.rightq.rightp.right.val = 3, q.right.val = 3,相等。
  9. p.right.leftq.right.left 入队:队列 = [null, null, null, null, null, null]。
  10. p.right.rightq.right.right 入队:队列 = [null, null, null, null, null, null, null, null]。
  11. 取出 nullnull,相等,继续。
  12. 最终队列为空,返回 true

代码实现

C#

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     public int val;
 *     public TreeNode left;
 *     public TreeNode right;
 *     public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
public class Solution {
    // 方法一:递归
    public bool IsSameTree(TreeNode p, TreeNode q) {
        // 如果两个节点都为空,则它们相同
        if (p == null && q == null) {
            return true;
        }
        
        // 如果其中一个节点为空而另一个不为空,则它们不相同
        if (p == null || q == null) {
            return false;
        }
        
        // 如果两个节点的值不相等,则它们不相同
        if (p.val != q.val) {
            return false;
        }
        
        // 递归地比较左子树和右子树
        return IsSameTree(p.left, q.left) && IsSameTree(p.right, q.right);
    }
    
    // 方法二:迭代(使用队列)
    public bool IsSameTreeIterative(TreeNode p, TreeNode q) {
        // 创建一个队列
        Queue<TreeNode> queue = new Queue<TreeNode>();
        
        // 将两棵树的根节点入队
        queue.Enqueue(p);
        queue.Enqueue(q);
        
        // 当队列不为空时
        while (queue.Count > 0) {
            // 取出两个节点
            TreeNode node1 = queue.Dequeue();
            TreeNode node2 = queue.Dequeue();
            
            // 如果两个节点都为空,继续下一轮循环
            if (node1 == null && node2 == null) {
                continue;
            }
            
            // 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回false
            if (node1 == null || node2 == null || node1.val != node2.val) {
                return false;
            }
            
            // 将左子节点入队
            queue.Enqueue(node1.left);
            queue.Enqueue(node2.left);
            
            // 将右子节点入队
            queue.Enqueue(node1.right);
            queue.Enqueue(node2.right);
        }
        
        // 如果队列为空,则返回true
        return true;
    }
}

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: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        # 如果两个节点都为空,则它们相同
        if not p and not q:
            return True
        
        # 如果其中一个节点为空而另一个不为空,则它们不相同
        if not p or not q:
            return False
        
        # 如果两个节点的值不相等,则它们不相同
        if p.val != q.val:
            return False
        
        # 递归地比较左子树和右子树
        return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
    
    # 方法二:迭代(使用队列)
    def isSameTreeIterative(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
        # 创建一个队列
        from collections import deque
        queue = deque([(p, q)])
        
        # 当队列不为空时
        while queue:
            # 取出两个节点
            node1, node2 = queue.popleft()
            
            # 如果两个节点都为空,继续下一轮循环
            if not node1 and not node2:
                continue
            
            # 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回False
            if not node1 or not node2 or node1.val != node2.val:
                return False
            
            # 将左子节点和右子节点入队
            queue.append((node1.left, node2.left))
            queue.append((node1.right, node2.right))
        
        # 如果队列为空,则返回True
        return True

C++

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    // 方法一:递归
    bool isSameTree(TreeNode* p, TreeNode* q) {
        // 如果两个节点都为空,则它们相同
        if (p == nullptr && q == nullptr) {
            return true;
        }
        
        // 如果其中一个节点为空而另一个不为空,则它们不相同
        if (p == nullptr || q == nullptr) {
            return false;
        }
        
        // 如果两个节点的值不相等,则它们不相同
        if (p->val != q->val) {
            return false;
        }
        
        // 递归地比较左子树和右子树
        return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
    }
    
    // 方法二:迭代(使用队列)
    bool isSameTreeIterative(TreeNode* p, TreeNode* q) {
        // 创建一个队列
        queue<TreeNode*> que;
        
        // 将两棵树的根节点入队
        que.push(p);
        que.push(q);
        
        // 当队列不为空时
        while (!que.empty()) {
            // 取出两个节点
            TreeNode* node1 = que.front();
            que.pop();
            TreeNode* node2 = que.front();
            que.pop();
            
            // 如果两个节点都为空,继续下一轮循环
            if (node1 == nullptr && node2 == nullptr) {
                continue;
            }
            
            // 如果其中一个节点为空而另一个不为空,或者两个节点的值不相等,则返回false
            if (node1 == nullptr || node2 == nullptr || node1->val != node2->val) {
                return false;
            }
            
            // 将左子节点入队
            que.push(node1->left);
            que.push(node2->left);
            
            // 将右子节点入队
            que.push(node1->right);
            que.push(node2->right);
        }
        
        // 如果队列为空,则返回true
        return true;
    }
};

执行结果

C#

  • 执行用时:76 ms,击败了 95.65% 的 C# 提交
  • 内存消耗:39.1 MB,击败了 91.30% 的 C# 提交

Python

  • 执行用时:32 ms,击败了 94.12% 的 Python3 提交
  • 内存消耗:15.0 MB,击败了 89.71% 的 Python3 提交

C++

  • 执行用时:0 ms,击败了 100.00% 的 C++ 提交
  • 内存消耗:9.9 MB,击败了 92.31% 的 C++ 提交

代码亮点

  1. 简洁的递归实现:递归方法的实现非常简洁,易于理解,体现了树问题解决的典型思路。
  2. 迭代方法的灵活性:提供了迭代方法作为递归的替代,避免了递归可能导致的栈溢出问题。
  3. 提前返回:在发现不满足条件时立即返回,避免了不必要的计算。
  4. 清晰的逻辑结构:代码结构清晰,逻辑易于理解,特别是在处理空节点和值比较方面。

常见错误分析

  1. 忽略空节点检查:在比较两棵树时,忘记检查节点是否为空,可能导致空指针异常。
  2. 只比较节点值:只比较节点的值而忽略了树的结构,这是不正确的,因为相同的树要求结构和值都相同。
  3. 递归终止条件错误:递归方法中的终止条件不正确,可能导致无限递归或错误的结果。
  4. 队列使用不当:在迭代方法中,队列的使用不当,如忘记将节点的子节点入队,或者入队顺序错误。

解法比较

方法时间复杂度空间复杂度优点缺点
递归O(n)O(h),h为树的高度实现简单,代码简洁对于深度很大的树,可能导致栈溢出
迭代(队列)O(n)O(n)避免了递归可能导致的栈溢出问题实现稍复杂,需要额外的队列空间

相关题目