题目
给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [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;
}
}