LeetCode 101. 对称二叉树

leetcode.cn/problems/sy...

programmercarl.com/0101.%E5%AF...

给你一个二叉树的根节点 root , 检查它是否轴对称。

示例 1:

ini 复制代码
输入: root = [1,2,2,3,4,4,3]
输出: true

示例 2:

csharp 复制代码
输入: root = [1,2,2,null,3,null,3]
输出: false

提示:

  • 树中节点数目在范围 [1, 1000]
  • -100 <= Node.val <= 100

进阶: 你可以运用递归和迭代两种方法解决这个问题吗?

首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!

对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树) ,所以在递归遍历的过程中,也是要同时遍历两棵树。

那么如何比较呢?

比较的是两个子树的里侧和外侧的元素是否相等。如图所示:

那么遍历的顺序应该是什么样的呢?

本题遍历只能是"后序遍历",因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。

正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。

但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。

其实后序也可以理解为是一种回溯,当然这是题外话,讲回溯的时候会重点讲的。

递归法

递归三部曲

  1. 确定递归函数的参数和返回值

因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。

返回值自然是bool类型。

代码如下:

c++ 复制代码
bool compare(TreeNode* left, TreeNode* right)
  1. 确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。

节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点

  • 左节点为空,右节点不为空,不对称,return false
  • 左不为空,右为空,不对称 return false
  • 左右都为空,对称,返回true

此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:

  • 左右都不为空,比较节点数值,不相同就return false

此时左右节点不为空,且数值也不相同的情况我们也处理了。

代码如下:

c++ 复制代码
if (left == NULL && right != NULL) return false;
else if (left != NULL && right == NULL) return false;
else if (left == NULL && right == NULL) return true;
else if (left->val != right->val) return false; // 注意这里我没有使用else

注意上面最后一种情况,我没有使用else,而是else if, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。

  1. 确定单层递归的逻辑

此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。

  • 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
  • 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
  • 如果左右都对称就返回true ,有一侧不对称就返回false 。

代码如下:

c++ 复制代码
    bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
    bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
    bool isSame = outside && inside;                    // 左子树:中、 右子树:中(逻辑处理)
    return isSame;

如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为"后序遍历"(尽管不是严格的后序遍历)。

最后递归的C++整体代码如下:

c++ 复制代码
    class Solution {
    public:
        bool compare(TreeNode* left, TreeNode* right) {
            // 首先排除空节点的情况
            if (left == NULL && right != NULL) return false;
            else if (left != NULL && right == NULL) return false;
            else if (left == NULL && right == NULL) return true;
            // 排除了空节点,再排除数值不相同的情况
            else if (left->val != right->val) return false;

            // 此时就是:左右节点都不为空,且数值相同的情况
            // 此时才做递归,做下一层的判断
            bool outside = compare(left->left, right->right);   // 左子树:左、 右子树:右
            bool inside = compare(left->right, right->left);    // 左子树:右、 右子树:左
            bool isSame = outside && inside;                    // 左子树:中、 右子树:中 (逻辑处理)
            return isSame;

        }
        bool isSymmetric(TreeNode* root) {
            if (root == NULL) return true;
            return compare(root->left, root->right);
        }
    };

kotlin 代码如下:

kotlin 复制代码
class Solution {
    fun isSymmetric(root: TreeNode?): Boolean {
        if (root == null) return true
        return interval(root.left, root.right)
    }

    fun interval(leftNode: TreeNode?, rightNode: TreeNode?): Boolean {
        if (leftNode == null && rightNode == null) {
            return true
        } else if (leftNode == null) {
            return false
        } else if (rightNode == null) {
            return false
        } else if (leftNode.`val` != rightNode.`val`) {
            return false
        }
        // 此时就是:左右节点都不为空,且数值相同的情况
        // 此时才做递归,做下一层的判断
        val outSame = interval(leftNode.left, rightNode.right)
        val inSame = interval(leftNode.right, rightNode.left)
        val same = outSame && inSame
        return same
    }
}

迭代法

这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。

这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历

使用队列

通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:

如下的条件判断和递归的逻辑是一样的。

代码如下:

c++ 复制代码
    class Solution {
    public:
        bool isSymmetric(TreeNode* root) {
            if (root == NULL) return true;
            queue<TreeNode*> que;
            que.push(root->left);   // 将左子树头结点加入队列
            que.push(root->right);  // 将右子树头结点加入队列
            
            while (!que.empty()) {  // 接下来就要判断这两个树是否相互翻转
                TreeNode* leftNode = que.front(); que.pop();
                TreeNode* rightNode = que.front(); que.pop();
                if (!leftNode && !rightNode) {  // 左节点为空、右节点为空,此时说明是对称的
                    continue;
                }

                // 左右一个节点不为空,或者都不为空但数值不相同,返回false
                if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                    return false;
                }
                que.push(leftNode->left);   // 加入左节点左孩子
                que.push(rightNode->right); // 加入右节点右孩子
                que.push(leftNode->right);  // 加入左节点右孩子
                que.push(rightNode->left);  // 加入右节点左孩子
            }
            return true;
        }
    };

kotlin 代码如下:

kotlin 复制代码
class Solution {
    /**
     * 迭代法
     * 使用普通队列
     */
    fun isSymmetric(root: TreeNode): Boolean {
        val deque: Queue<TreeNode?> = LinkedList<TreeNode?>()
        deque.offer(root.left)
        deque.offer(root.right)
        while (!deque.isEmpty()) {
            val leftNode = deque.poll()
            val rightNode = deque.poll()
            if (leftNode == null && rightNode == null) {
                continue
            }
            if (leftNode == null || rightNode == null || leftNode.`val` != rightNode.`val`) {
                return false
            }
            deque.offer(leftNode.left)
            deque.offer(rightNode.right)
            deque.offer(leftNode.right)
            deque.offer(rightNode.left)
        }
        return true
    }
}

使用栈

细心的话,其实可以发现,这个迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。

只要把队列原封不动的改成栈就可以了,我下面也给出了代码。

c++ 复制代码
    class Solution {
    public:
        bool isSymmetric(TreeNode* root) {
            if (root == NULL) return true;
            stack<TreeNode*> st; // 这里改成了栈
            st.push(root->left);
            st.push(root->right);
            while (!st.empty()) {
                TreeNode* rightNode = st.top(); st.pop();
                TreeNode* leftNode = st.top(); st.pop();
                if (!leftNode && !rightNode) {
                    continue;
                }
                if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
                    return false;
                }
                st.push(leftNode->left);
                st.push(rightNode->right);
                st.push(leftNode->right);
                st.push(rightNode->left);
            }
            return true;
        }
    };
相关推荐
可编程芯片开发2 小时前
基于MPPT最大功率跟踪的离网光伏发电系统Simulink建模与仿真
算法
AI科技星2 小时前
线性算子不是空间映射函数,是全域双螺旋场之间拉伸、旋转、耦合、坍缩的跨空间标准化变换载体《全域数学vs传统数学:人类文明进阶200讲》第80讲
线性代数·算法·矩阵·数据挖掘·回归·乖乖数学·全域数学
米罗篮2 小时前
矩阵快速幂 (Exponentiation By Squaring Applied To Matrices)
c++·线性代数·算法·矩阵
dream_home84072 小时前
图像算法模型NPU适配与算法服务实战指南
人工智能·python·算法·npu 图像服务
大鱼>3 小时前
多宠物家庭智能管理平台:云端架构与多设备协同实战
python·算法·iot·宠物
To_OC4 小时前
LC 22 括号生成:刷完这道题,我终于搞懂回溯剪枝了
javascript·算法·leetcode
To_OC4 小时前
LC 39 组合总和:回溯入门必刷题,我踩过的两个坑都在这了
javascript·算法·leetcode
数字杂技师4 小时前
每条推荐都很准,为什么我还是越刷越无聊?
算法
兰令水4 小时前
hot100【acm版】【2026.7.14打卡-java版本】
java·数据结构·算法·leetcode·面试