剑指 Offer 28. 对称的二叉树 - leetcode 剑指offer系列

题目难度: 简单

原题链接

今天继续更新剑指 offer 系列, 这道题同样可以用递归或迭代来做, 这里提供两种递归和一种迭代的方法帮助大家拓展思路

老样子晚上 6 点 45 分准时更新公众号 每日精选算法题, 大家记得关注哦~ 另外在公众号里回复 offer 就能看到剑指 offer 系列当前连载的所有文章了

题目描述

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

例如,二叉树  [1,2,2,3,4,4,3] 是对称的。

    1
   / \
  2   2
 / \ / \
3  4 4  3

但是下面这个  [1,2,2,null,3,null,3] 则不是镜像对称的:

    1
   / \
  2   2
   \   \
   3    3
  • 0 <= 节点个数 <= 1000

题目样例

示例 1

输入

root = [1,2,2,3,4,4,3]

输出

true

示例 2

输入

root = [1,2,2,null,3,null,3]

输出

false

题目思考

  1. 生成镜像比较可以吗, 直接比较呢?
  2. 如果限制只能用递归或者迭代, 如何解决?

解决方案

方案 1

思路
  • 根据题目描述, 最自然的思路就是生成原树的镜像后, 再逐个节点比较是否完全相等即可
  • 对于生成镜像部分不清楚的同学可以回顾下上篇文章 剑指 Offer 27. 二叉树的镜像 - leetcode 剑指offer系列
  • 需要注意这里生成的镜像不能直接在原树基础上改动了, 需要生成新的节点, 也即新建一个节点使用原来的节点值, 不然下面比较的时候就是和自身比较, 肯定总是相等了
  • 逐个节点比较也很简单, 如果其中某个节点为空, 则必须另一个也是空; 而如果两个节点都非空的话, 则要比较它们的值, 并传入左右子节点递归比较子树的相等性
复杂度
  • 时间复杂度 O(N)
    • 所有节点只需要遍历一遍
  • 空间复杂度 O(N)
    • 递归栈的消耗, 以及镜像树的空间
代码
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        # 方法1: 先拿到镜像, 然后逐节点比较
        def getMirror(root):
            if not root:
                return root
            # 注意这里要新建node, 不能原地修改!!
            newNode = TreeNode(root.val)
            newNode.left, newNode.right = getMirror(root.right), getMirror(
                root.left)
            return newNode

        mirror = getMirror(root)

        def ismatch(n1, n2):
            if not n1 or not n2:
                return not n1 and not n2
            # 这里需要比较n1和n2树精确匹配
            return n1.val == n2.val and ismatch(n1.left, n2.left) and ismatch(
                n1.right, n2.right)

        # 当原树和镜像树完全匹配的时候才对称
        return ismatch(root, mirror)

方案 2

思路
  • 回顾方案 1, 我们需要先生成镜像再比较, 有没有直接一遍比较就能得出结果的方法呢?
  • 答案也是有的, 注意题目描述, 对称时左子树的最左节点等于右子树的最右节点, 就像中间有个对称轴一样
  • 所以我们可以传入当前左右子树上处于对称位置的两个节点, 比较它们是否相等, 相等的话再递归调用比较方法, 比较(左节点的右子节点, 右节点的左子节点)以及(左节点的左子节点, 右节点的右子节点), 也即子树上的两对对称节点, 如果都满足对称性的话则说明整个树对称
复杂度
  • 时间复杂度 O(N)
    • 所有节点只需要遍历一遍
  • 空间复杂度 O(N)
    • 递归栈的消耗
代码
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        # 方法2: 递归直接比较
        if not root:
            return True

        def symm(left, right):
            if not left or not right:
                # left和right某一个是空的时候, 需要两个都是空才算对称
                return not left and not right
            # 对称需要满足当前两节点值相等, 且左边的左节点=右边的右节点 & 左边的右节点=右边的左节点
            return left.val == right.val and symm(
                left.left, right.right) and symm(left.right, right.left)

        # 此时root一定非空, 直接传入左右子节点判断对称性即可
        return symm(root.left, root.right)

方案 3

思路
  • 接下来我们考虑迭代做法
  • 根据方案 2, 我们需要比较左右子树处于对称位置的节点
  • 所以我们完全可以将这些对称节点放在两个队列中, 分别代表左右子树中的节点
  • 然后仍然按照方案 2 的顺序将子节点依次加入队列, 保证比较的是处于对称位置的节点即可
  • 注意空节点也需要被加入队列, 用作占位符, 不然对于用例 2 就会错误地判断成对称的了
  • 下面代码中有详细解释, 方便大家理解
复杂度
  • 时间复杂度 O(N)
    • 所有节点只需要遍历一遍
  • 空间复杂度 O(N)
    • 两个队列各自需要存 N/2 个节点
代码
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        # 方法3: 迭代, 使用两个队列存左子树和右子树的节点
        if not root:
            return True
        leftq, rightq = [root.left], [root.right]
        # 这里可以优化为只使用一个下标, 因为下标总是相等的. 这里为了更容易理解, 采用两个下标
        i, j = 0, 0
        while i < len(leftq) or j < len(rightq):
            if i == len(leftq) or j == len(rightq):
                # 其中一方队列遍历完了, 肯定不对称
                return False
            left, right = leftq[i], rightq[j]
            i += 1
            j += 1
            if not left or not right:
                # 某一边节点为空的情况
                if not left and not right:
                    # 必须两边都是空才可以继续循环
                    continue
                else:
                    # 否则肯定不对称
                    return False
            else:
                if left.val != right.val:
                    # 值不相等, 不对称
                    return False
                # 注意这里顺序, 保证左边的right和右边的left比较, 左边的left和右边的right比较 (同方法2思路)
                leftq.append(left.right)
                leftq.append(left.left)
                rightq.append(right.left)
                rightq.append(right.right)
        return True

大家可以在下面这些地方找到我~😊

我的知乎专栏

我的 CSDN

我的 Leetcode

我的牛客网博客

我的公众号: 每日精选算法题, 欢迎大家扫码关注~😊

每日精选算法题 - 微信扫一扫关注我

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值