LEETCODE 101Easy

对称二叉树

对称不是一棵树的性质,而是两棵子树互为镜像的性质,问题从一元变成了二元。

问题拆解

判断一棵树是否轴对称,容易卡在“对称”到底该怎么递归上。如果只盯着单个节点,会发现根本无从下手——对称描述的是根节点左右两半之间的关系,而不是某一个节点自己的属性。

所以要把问题改写成一个二元判断:左子树和右子树是否互为镜像。一旦这样定义,递归的结构就清楚了。

两棵树互为镜像的条件是:根值相等,且“甲的左”镜像“乙的右”、“甲的右”镜像“乙的左”。

递归比较镜像

public boolean isSymmetric(TreeNode root) {
    return root == null || mirror(root.left, root.right);
}

private boolean mirror(TreeNode a, TreeNode b) {
    if (a == null && b == null) return true;
    if (a == null || b == null) return false;
    return a.val == b.val
        && mirror(a.left, b.right)
        && mirror(a.right, b.left);
}
def isSymmetric(root):
    def mirror(a, b):
        if not a and not b:
            return True
        if not a or not b:
            return False
        return (a.val == b.val
                and mirror(a.left, b.right)
                and mirror(a.right, b.left))

    return mirror(root.left, root.right) if root else True
func isSymmetric(root *TreeNode) bool {
    if root == nil {
        return true
    }
    return mirror(root.Left, root.Right)
}

func mirror(a, b *TreeNode) bool {
    if a == nil && b == nil {
        return true
    }
    if a == nil || b == nil {
        return false
    }
    return a.Val == b.Val &&
        mirror(a.Left, b.Right) &&
        mirror(a.Right, b.Left)
}
pub fn is_symmetric(root: Option<Rc<RefCell<TreeNode>>>) -> bool {
    fn mirror(a: &Option<Rc<RefCell<TreeNode>>>, b: &Option<Rc<RefCell<TreeNode>>>) -> bool {
        match (a, b) {
            (None, None) => true,
            (Some(a), Some(b)) => {
                let (a, b) = (a.borrow(), b.borrow());
                a.val == b.val && mirror(&a.left, &b.right) && mirror(&a.right, &b.left)
            }
            _ => false,
        }
    }
    root.map_or(true, |r| {
        let r = r.borrow();
        mirror(&r.left, &r.right)
    })
}

注意递归调用里的交叉方式:a.leftb.righta.rightb.left。正是这个交叉,把“镜像”的语义落到了代码上。两个都为空说明对称到底了,只有一个为空则结构不对称。

迭代版思路

也可以用一个队列,每次成对取出两个应当镜像的节点做比较,再把它们的孩子按 (a.left, b.right)(a.right, b.left) 成对入队。逻辑和递归完全一致,只是把调用栈换成了显式队列。

复杂度

指标 复杂度 原因
时间 O(n) 每个节点最多被比较一次
空间 O(h) 递归栈深度等于树高

可以迁移的模式

  • 当“一棵树的性质”难以直接递归时,试着把它翻译成“两棵子树之间的关系”;
  • 涉及镜像、对折、回文的问题,往往要让两个指针以相反方向同步推进;
  • 二元递归的终止条件要覆盖“都空”和“只有一个空”两种情况。

把单参数递归升级成双参数,是处理“对比类”树问题的常用一招。