LeetCode 101. 对称二叉树 思考分析

136 阅读4分钟

题目

给定一个二叉树,检查它是否是镜像对称的。

例如,二叉树 [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,否则填val;
当此层所有结点都被遍历了(包括空结点),观察子数组是否对称。
然后更新queue,如果该结点为空结点,则它的子结点也是空结点,如果该结点不是空结点,它的子结点根据真实情况填,如果为空也填入NULL。
不过这样好像超时了,也就无法验证了。
退出循环的条件是,该层的所有结点都是空结点

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        queue<TreeNode*> que;
        if(root!=NULL) que.push(root);
        while(1)
        {
            //该层结点元素个数 = 该层队列元素
            int size = que.size();
            vector<int> vec;
            int null_times=0;
            //这里要使用固定大小的size,不能使用que.size(),因为在处理中que.size是不断变化的
            //将这层元素送入队列中并依次从队首向队尾将元素出队列,每个元素出队列的同时又将其不为空的子结点送入队列
            for(int i =0;i<size;i++)
            {
                TreeNode* node = que.front();
                //将队首元素送入该层结果
                que.pop();
                if(node!=NULL) 
                {
                    vec.push_back(node->val);
                    //将左右孩子结点入队列,作为下一层的元素
                    if(node->left!=NULL) que.push(node->left);
                    else que.push(NULL);
                    if(node->right!=NULL) que.push(node->right);
                    else que.push(NULL);
                }
                else
                {
                    null_times++;
                    vec.push_back(-2147483648);
                    //将左右孩子结点入队列,作为下一层的元素
                    que.push(NULL);
                    que.push(NULL);
                } 
            }
            if(null_times == size) break;
            int vecsize=vec.size();
            for(int j=0;j<vecsize/2+1;j++)
            {
                if(vec[j]!=vec[vecsize-1-j]) return false;
            }
        }
        return true;
    }
};

思路二,构造翻转二叉树,判断是否相同

先构造一棵反转二叉树,然后按顺序遍历这两棵树,判断是否相同。
Leetcode226. 翻转二叉树(递归、迭代、层序三种解法)
LeetCode 100. 相同的树 思考分析
不过此处有一个问题,我们当时翻转二叉树的操作是在输入的二叉树上进行修改的。这样如果直接isSameTree(root,invertTree(root));
得到的结果都是true。所以我们需要先深复制一个新的二叉树,然后在新的二叉树上完成翻转操作,然后将翻转后的二叉树与原本的二叉树进行判断。
关于深复制一棵二叉树:
LintCode 375. 克隆二叉树(深复制)

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* invertTree(TreeNode* root) {
        queue<TreeNode*> que;
        if(root!=NULL) que.push(root);
        while(!que.empty())
        {
            int size = que.size();
            for(int i =0;i<size;i++)
            {
                TreeNode* node = que.front();
                que.pop();
                TreeNode* tmp;
                tmp = node->left;
                node->left = node->right;
                node->right = tmp;
                //将左右孩子结点入队列,作为下一层的元素
                if(node->left) que.push(node->left);
                if(node->right) que.push(node->right);
            }
        }
        return root;
    }
    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p && q)
        {
            if(p->val == q->val)
            {
                return isSameTree(p->right,q->right) && isSameTree(p->left,q->left);
            }
            else
            {
                return false;
            } 
        }
        else if(!p && !q)
        {
            return true;
        }
        return false;
    }
    TreeNode * preorder(TreeNode * root){
        if(root==NULL) return NULL;
        TreeNode * ans;
        ans=new TreeNode(root->val);
        if(root->left!=NULL){
            ans->left=preorder(root->left);
        }
        if(root->right!=NULL){
            ans->right=preorder(root->right);
        }
        return ans;
    }
    bool isSymmetric(TreeNode* root) {
        TreeNode *roota;
        roota=preorder(root);
        return isSameTree(root,invertTree(roota));
    }
};

在这里插入图片描述

参考其他思路

之前的构造的思路会导致空间严重浪费,并且时间耗费也较多。
关于二叉树是否对称,我们要比较的是根结点的左子树与右子树是不是相互翻转的。递归遍历的时候要同时遍历两棵树,比较两棵子树的里侧和外侧元素是否相同
本题的遍历顺序为后序遍历,因为要通过递归函数的返回值来判断两个子树的内测结点与外侧结点是否相等。
一棵树遍历顺序为左右中,另一棵树遍历顺序是右左中。这里可以理解为一种回溯的思想。

递归法

1、确定递归地参数和返回值
参数:该结点的左子树结点、右子树结点
返回值:bool类型
bool compare(TreeNode* left,TreeNode* right)
2、确定终止条件
1、左右都为空,返回true
2、左右只有一个为空,返回false
3、左右结点均不为空,比较结点数值,不相同返回false
4、左右结点均不为空,数值相同返回true

if(left == NULL && right==NULL) return true;
else if(left!=NULL && right==NULL) return false;
else if(right!=NULL && left==NULL) return false;
else if(left->val != right->val) return false;

3、确定单层递归逻辑
处理左右结点皆不为空且数值相同的情况。
1、比较二叉树外侧是否对称:传入左结点的左孩子,右结点的右孩子。
2、比较内侧是否对称,传入左结点的右孩子,右结点的左孩子。
3、如果左右都对称就返回true,否则返回false

bool outside = compare(left->left,right->right);	
bool inside = compare(left->right,right->left);
bool isSame = outside && inside;		
return isSame

完整代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool compare(TreeNode* left,TreeNode* right)
    {
        if(left == NULL && right==NULL) return true;
        else if(left!=NULL && right==NULL) return false;
        else if(right!=NULL && left==NULL) return false;
        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);
    }
};

在这里插入图片描述

迭代法

这一题的本质是判断两个树是否相互翻转,并非简单的遍历。这里可以使用队列来比较两个树(根结点的左右子树)是否相互翻转。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
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;
    }
};