Leetcode【递归 栈 队列 双指针】| 101. 对称二叉树

题目

给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [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
进阶:你可以运用递归和迭代两种方法解决这个问题吗?

解题

思路

 刚开始会想到用中序遍历结果来比较是否对称,但此方法不可行,因为中序遍历结果存储的是每个结点的值,不同结点值可能会相同,加上还有空结点,所以结果的值相同不一定树的结构相同。
 所以采用双指针的思想,用正好相反的两种中序遍历顺序(即对称的顺序),在遍历的过程中同步比较一个个判断是否对称。依次按顺序遍历树的左半部分和右半部分,在两边镜像遍历的过程中一一对应比较。
 镜像对称说明树的左子树和右子树必然完成对称。所以用两个指针,一个指针从左子树的根节点(root.left)开始,以先左后右的方式遍历;另一个指针从右子树的根节点(root.right)开始,以先右后左的方式遍历。两个指针同步移动,这样两个指针每次移动到的结点都正好在树中镜像对称。比较两个指针对应的结点是否一样即可,出现不一样的情况就说明该树不是对称二叉树。
 遍历的方法使用DFS深度优先遍历或BFS广度优先遍历(两种遍历方法详解),实现主要有递归和迭代两种,迭代用栈或队列实现。

递归

 设两个指针leftnode和rightnode,分别从树的左和右出发。每次递归比较“左的左”与"右的右",“左的右”与“右的左”这样镜像对称的位置,看是否是相同的结点情况。

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null) return true;
        //递归 设置两个指针 分别从左子树和右子树镜像出发,看是否相等
        return isEqual(root.left,root.right);
    }
    public boolean isEqual(TreeNode leftnode, TreeNode rightnode){
        if(rightnode == null && leftnode == null) return true;
        if(leftnode == null || rightnode == null) return false;
        if(leftnode.val != rightnode.val) return false;
        return isEqual(leftnode.left,rightnode.right) && isEqual(leftnode.right,rightnode.left);
    }
}

在这里插入图片描述

迭代

DFS栈实现

两个栈

 用两个栈分别深度优先遍历根节点的左子树和右子树。左子树按照左根右的顺序,右子树按照右根左的顺序,这样等于将右子树水平翻转与左子树进行比较,遍历出的左子树和右子树正好是镜像的一一对应的。因为考虑到空结点的问题,所以也是边遍历边比较,两个栈同步遍历,每遍历一个结点进行一次比较,判断不相等的情况,如果没有两栈遍历的结点一直一致,则是对称二叉树。

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null) return true;
        //迭代 两个栈 同时dfs root的左子树和右子树 左子树先右后左进先左后右出,右子树先左后右进先右后左出 
        Stack<TreeNode> lstack = new Stack<>();//左子树的dfs栈
        Stack<TreeNode> rstack = new Stack<>();//右子树的df栈
        //另一种写法必须加上下面两行判断
        //if(root.left == null && root.right == null) return true;
        //if(root.left == null || root.right == null) return false;
        lstack.push(root.left);
        rstack.push(root.right);
        while(!lstack.isEmpty() && !rstack.isEmpty()){
            TreeNode nodel = lstack.pop();
            TreeNode noder = rstack.pop();
            if(nodel == null && noder == null){
                continue;
            }else if(nodel == null && noder != null){
                return false;
            }else if(nodel != null && noder == null){
                return false;
            }else if(nodel.val != noder.val){
                return false;
            }
            //左子树先右后左进
            lstack.push(nodel.right);
            lstack.push(nodel.left);
            //右子树先左后右进,这样出栈时顺序时先右后左
            rstack.push(noder.left);
            rstack.push(noder.right);
            
            /*另一种写法
            if(lstack.isEmpty() || rstack.isEmpty()) return false;
            TreeNode nodel = lstack.pop();
            TreeNode noder = rstack.pop();
            if(nodel.val != noder.val){
                return false;
            }
            //左子树先右后左进  右子树先左后右进
            if(nodel.right != null && noder.left != null){
                lstack.push(nodel.right);
                rstack.push(noder.left);
            }else if(nodel.right== null && noder.left == null){
                //注:这里不能写continue;因为如果continue就直接进入下一个while循环,下一个if()就被跳过了
            }else{
                return false;
            }
            
            if(nodel.left != null && noder.right != null){
                lstack.push(nodel.left);
                rstack.push(noder.right);
            }else if(nodel.left == null && noder.right == null){
                
            }else{
                return false;
            }
            */
        }
        return true;
    }
}

在这里插入图片描述

一个栈

 不用两个栈分别存储两半的遍历结果,直接用一个栈来同时进行两种遍历(一种遍历右根左,另一种左根右),只需从根或直接从根的左右结点出发。每次入栈都把两种遍历同步都入到一个栈中,因为遍历的顺序问题,所以两种遍历每一步对应的两个结点在树中是镜像对应的,而又因为是同步(每次同步入栈),这两个结点在栈中的顺序是连续的,所以出栈时每次也连续出两个结点,比较这两个结点是否时相同的结点即可。

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null) return true;
        //用一个队列/栈 两个指针同时从根节点的左和右镜像出发,压入两个结点,然后看依次弹出的两个成对结点是否一致
        Stack<TreeNode> stack = new Stack<>();

        //初始将根节点的左/右结点加入
        stack.push(root.left);
        stack.push(root.right);
        while(!stack.isEmpty()){
            //每次弹出挨着的两个结点
            TreeNode l = stack.pop();
            TreeNode r = stack.pop();
            //比较两个结点是否相同,相同可继续,不相同返回结果
            if(l == null && r == null){
                continue;
            }else if(l == null || r == null){
                return false;
            }else if(l.val != r.val){
                return false;
            }
            //弹出结点后,将两种便利的下两个镜像对应子结点分别同步入栈
            //左的左 对应 右的右
            stack.push(l.left);
            stack.push(r.right);
            //左的右 对应 右的左
            stack.push(l.right);
            stack.push(r.left);
        }
        return true;
    }
}

在这里插入图片描述

BFS队列实现

用一个队列,跟上述一个栈的思路一样。因为我们重在比较镜像对称的两个结点是否一样,所以到底是深度优先还是广度优先的遍历方式都可以。、

class Solution {
    public boolean isSymmetric(TreeNode root) {
        if(root == null) return true;
        //用一个队列/栈 两个指针同时从根节点的左和右镜像出发,压入两个结点,然后看依次弹出的两个成对结点是否一致
        Queue<TreeNode> queue = new LinkedList<>();
        TreeNode l = root;//从左出发的指针
        TreeNode r = root;//从右出发的指针
        //初始将根节点的左/右结点加入
        queue.offer(l.left);
        queue.offer(r.right);
        while(!queue.isEmpty()){
            //每次弹出挨着的两个结点
            l = queue.poll();
            r = queue.poll();
            if(l == null && r == null){
                continue;
            }else if(l == null || r == null){
                return false;
            }else if(l.val != r.val){
                return false;
            }
            
            queue.offer(l.left);
            queue.offer(r.right);
            
            queue.offer(l.right);
            queue.offer(r.left);
        }
        return true;
    }
}

在这里插入图片描述

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值