判断二叉树是否对称?附递归C++、JAVA、PYTHON代码

2022-07-1311:21:57数据结构与算法Comments1,266 views字数 10615阅读模式

101. 对称二叉树

题目地址:https://leetcode-cn.com/problems/symmetric-tree/文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

给定一个二叉树,检查它是否是镜像对称的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

判断二叉树是否对称?附递归C++、JAVA、PYTHON代码

思路

首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了其实我们要比较的是两个树(这两个树是根节点的左右子树),所以在递归遍历的过程中,也是要同时遍历两棵树。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

那么如果比较呢?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

比较的是两个子树的里侧和外侧的元素是否相等。如图所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

判断二叉树是否对称?附递归C++、JAVA、PYTHON代码

那么遍历的顺序应该是什么样的呢?文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

其实后序也可以理解为是一种回溯,当然这是题外话,讲回溯的时候会重点讲的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

说到这大家可能感觉我有点啰嗦,哪有这么多道理,上来就干就完事了。别急,我说的这些在下面的代码讲解中都有身影。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

那么我们先来看看递归法的代码应该怎么写。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

递归法

递归三部曲文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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

因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

返回值自然是bool类型。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

bool compare(TreeNode* left, TreeNode* right)
  1. 确定终止条件

要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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

此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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

此时左右节点不为空,且数值也不相同的情况我们也处理了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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,而是elseif, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

  1. 确定单层递归的逻辑

此时才进入单层递归的逻辑,单层递归的逻辑就是处理 右节点都不为空,且数值相同的情况。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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

代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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

如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

最后递归的C++整体代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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);
    }
};

我给出的代码并不简洁,但是把每一步判断的逻辑都清楚的描绘出来了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

如果上来就看网上各种简洁的代码,看起来真的很简单,但是很多逻辑都掩盖掉了,而题解可能也没有把掩盖掉的逻辑说清楚。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

当然我可以把如上代码整理如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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;
        else return compare(left->left, right->right) && compare(left->right, right->left);

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

这个代码就很简洁了,但隐藏了很多逻辑,条理不清晰,而且递归三部曲,在这里完全体现不出来。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

所以建议大家做题的时候,一定要想清楚逻辑,每一步做什么。把道题目所有情况想到位,相应的代码写出来之后,再去追求简洁代码的效果。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

迭代法

这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

使用队列

通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

判断二叉树是否对称?附递归C++、JAVA、PYTHON代码

如下的条件判断和递归的逻辑是一样的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

代码如下:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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;
    }
};

使用栈

细心的话,其实可以发现,这个迭代法,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

只要把队列原封不动的改成栈就可以了,我下面也给出了代码。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

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* leftNode = st.top(); st.pop();
            TreeNode* rightNode = 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;
    }
};

总结

这次我们又深度剖析了一道二叉树的“简单题”,大家会发现,真正的把题目搞清楚其实并不简单,leetcode上accept了和真正掌握了还是有距离的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

我们介绍了递归法和迭代法,递归依然通过递归三部曲来解决了这道题目,如果只看精简的代码根本看不出来递归三部曲是如果解题的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

在迭代法中我们使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

如果已经做过这道题目的同学,读完文章可以再去看看这道题目,思考一下,会有不一样的发现!文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

相关题目推荐

  • 100.相同的树
  • 572.另一个树的子树

其他语言版本

Java

    /**
     * 递归法
     */
    public boolean isSymmetric1(TreeNode root) {
        return compare(root.left, root.right);
    }

    private boolean compare(TreeNode left, TreeNode right) {

        if (left == null && right != null) {
            return false;
        }
        if (left != null && right == null) {
            return false;
        }

        if (left == null && right == null) {
            return true;
        }
        if (left.val != right.val) {
            return false;
        }
        // 比较外侧
        boolean compareOutside = compare(left.left, right.right);
        // 比较内侧
        boolean compareInside = compare(left.right, right.left);
        return compareOutside && compareInside;
    }

    /**
     * 迭代法
     * 使用双端队列,相当于两个栈
     */
    public boolean isSymmetric2(TreeNode root) {
        Deque<TreeNode> deque = new LinkedList<>();
        deque.offerFirst(root.left);
        deque.offerLast(root.right);
        while (!deque.isEmpty()) {
            TreeNode leftNode = deque.pollFirst();
            TreeNode rightNode = deque.pollLast();
            if (leftNode == null && rightNode == null) {
                continue;
            }
//            if (leftNode == null && rightNode != null) {
//                return false;
//            }
//            if (leftNode != null && rightNode == null) {
//                return false;
//            }
//            if (leftNode.val != rightNode.val) {
//                return false;
//            }
            // 以上三个判断条件合并
            if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
                return false;
            }
            deque.offerFirst(leftNode.left);
            deque.offerFirst(leftNode.right);
            deque.offerLast(rightNode.right);
            deque.offerLast(rightNode.left);
        }
        return true;
    }

    /**
     * 迭代法
     * 使用普通队列
     */
    public boolean isSymmetric3(TreeNode root) {
        Queue<TreeNode> deque = new LinkedList<>();
        deque.offer(root.left);
        deque.offer(root.right);
        while (!deque.isEmpty()) {
            TreeNode leftNode = deque.poll();
            TreeNode rightNode = deque.poll();
            if (leftNode == null && rightNode == null) {
                continue;
            }
//            if (leftNode == null && rightNode != null) {
//                return false;
//            }
//            if (leftNode != null && rightNode == null) {
//                return false;
//            }
//            if (leftNode.val != rightNode.val) {
//                return false;
//            }
            // 以上三个判断条件合并
            if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
                return false;
            }
            // 这里顺序与使用Deque不同
            deque.offer(leftNode.left);
            deque.offer(rightNode.right);
            deque.offer(leftNode.right);
            deque.offer(rightNode.left);
        }
        return true;
    }

Python

递归法:文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        return self.compare(root.left, root.right)
        
    def compare(self, left, right):
        #首先排除空节点的情况
        if left == None and right != None: return False
        elif left != None and right == None: return False
        elif left == None and right == None: return True
        #排除了空节点,再排除数值不相同的情况
        elif left.val != right.val: return False
        
        #此时就是:左右节点都不为空,且数值相同的情况
        #此时才做递归,做下一层的判断
        outside = self.compare(left.left, right.right) #左子树:左、 右子树:右
        inside = self.compare(left.right, right.left) #左子树:右、 右子树:左
        isSame = outside and inside #左子树:中、 右子树:中 (逻辑处理)
        return isSame

迭代法:使用队列文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

import collections
class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        queue = collections.deque()
        queue.append(root.left) #将左子树头结点加入队列
        queue.append(root.right) #将右子树头结点加入队列
        while queue: #接下来就要判断这这两个树是否相互翻转
            leftNode = queue.popleft()
            rightNode = queue.popleft()
            if not leftNode and not rightNode: #左节点为空、右节点为空,此时说明是对称的
                continue
            
            #左右一个节点不为空,或者都不为空但数值不相同,返回false
            if not leftNode or not rightNode or leftNode.val != rightNode.val:
                return False
            queue.append(leftNode.left) #加入左节点左孩子
            queue.append(rightNode.right) #加入右节点右孩子
            queue.append(leftNode.right) #加入左节点右孩子
            queue.append(rightNode.left) #加入右节点左孩子
        return True

迭代法:使用栈文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        st = [] #这里改成了栈
        st.append(root.left)
        st.append(root.right)
        while st:
            leftNode = st.pop()
            rightNode = st.pop()
            if not leftNode and not rightNode:
                continue
            if not leftNode or not rightNode or leftNode.val != rightNode.val:
                return False
            st.append(leftNode.left)
            st.append(rightNode.right)
            st.append(leftNode.right)
            st.append(rightNode.left)
        return True

来源于代码随想录 ,作者程序员Carl文章源自菜鸟学院-https://www.cainiaoxueyuan.com/suanfa/24957.html

  • 本站内容整理自互联网,仅提供信息存储空间服务,以方便学习之用。如对文章、图片、字体等版权有疑问,请在下方留言,管理员看到后,将第一时间进行处理。
  • 转载请务必保留本文链接:https://www.cainiaoxueyuan.com/suanfa/24957.html

Comment

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定