题目
出自牛客网题霸系列
链接:https://www.nowcoder.com/practice/1b0b7f371eae4204bc4a7570c84c2de1?tpId=117&&tqId=34937&rp=1&ru=/ta/job-code-high&qru=/ta/job-code-high/question-ranking
题目被百度、字节、快手等公司考察过。
面试现场
对于树中的任意两个对称节点L、R,必然有:
L 和 R 节点的值相等;
L 的左节点和 R 的右节点对称
L 的右节点和 R 的左节点对称
所以知道对称二叉树的定义后,可以看出来对称二叉树的定义是递归的,所以考虑从顶部节点开始往下递归,判断递归的每队节点是否对称,只要有一对节点不对称,那么整颗树就不是对称,反之,如果每对节点都对称,那么整棵树是对称。
递归三步曲
递归第一步
确定递归参数的入参和返回值
入参:因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。返回值:bool类型
代码:
public boolean recur(TreeNode left, TreeNode right)
递归第二步
递归的终止条件,如何结束
比较的两颗树记录为 L 和 R不对称的情况,直接返回 false:
L 为空 R 不为空 必然不对称
L 不为空 R 为空 必然不对称
L 和 R 都不为空 但是 L 和 R 的节点值不同 不对称
对称的情况 直接返回 true:
L 和 R 都为空 对称
代码如下:
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;
上述代码为了好理解,简化一下代码,等价于:
//如果左右子节点都为空,说明当前节点是叶子节点,返回true
if (left == null && right == null)
return true;
//如果当前节点只有一个子节点或者有两个子节点,但两个子节点的值不相同,直接返回false
if (left == null || right == null || left.val != right.val)
return false;
可以看到还有一种情况,L 和 R 都不为空,L 和 R 节点值相同 前面没有提到,这种情况无法直接终止递归,因为还需要继续递归判断
L 的左子树和 R 的右子树是否对称
L 的右子树和 R 的左子树是否对称 根据这两个条件的结果,才可以知道 L 和 R 都不为空,L 和 R 节点值相同 的情况 L 和 R 是否对称。
也就是要在这个条件下进入下一层递归,执行递推的工作。简称:再次进入递归
递归第三步
再次进入递归,执行递推的工作
根据第二步可知,递归第三步是为了进入递归;去判断:
L 的左子树和 R 的右子树是否对称
L 的右子树和 R 的左子树是否对称
递归嘛~也就是自己调用自己的函数。
recur(left.left, right.right); // L 的左子树和 R 的右子树是否对称
recur(left.right, right.left); // L 的右子树和 R 的左子树是否对称
retrun recur(left.left, right.right) && recur(left.right, right.left); // 两者都返回true,那么 L 和 R才是对称的。
递归解法图解思路
递归解法动画
递归解法代码
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);
}
};
Java
public boolean isSymmetric(TreeNode root) {
if (root == null)
return true;
//从两个子节点开始判断
return recur(root.left, root.right);
}
public boolean recur(TreeNode left, TreeNode right) {
//如果左右子节点都为空,说明当前节点是叶子节点,返回true
if (left == null && right == null)
return true;
//如果当前节点只有一个子节点或者有两个子节点,但两个子节点的值不相同,直接返回false
if (left == null || right == null || left.val != right.val)
return false;
//然后左子节点的左子节点和右子节点的右子节点比较,左子节点的右子节点和右子节点的左子节点比较
return recur(left.left, right.right) && recur(left.right, right.left);
}
Python
class Solution(object):
def isSymmetric(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if not root:
return True
def dfs(left,right):
# 递归的终止条件是两个节点都为空
# 或者两个节点中有一个为空
# 或者两个节点的值不相等
if not (left or right):
return True
if not (left and right):
return False
if left.val!=right.val:
return False
return dfs(left.left,right.right) and dfs(left.right,right.left)
# 用递归函数,比较左节点,右节点
return dfs(root.left,root.right)
Js
/**
* Definition for a binary tree node.
* function TreeNode(val) {
* this.val = val;
* this.left = this.right = null;
* }
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isSame = function(root1,root2){
let r1,r2;
if(root1==null || root2==null)
{
if(root1==null && root2==null)
return true;
else
return false;
}
if(root1.val!==root2.val)
return false;
r1 = isSame(root1.left,root2.right);
r2 = isSame(root1.right,root2.left);
return r1 & r2;
}
var isSymmetric = function(root) {
if(root == null)
return true;
//判断根左子树是否和根右子树对称 递归的那l.left和r.right 以及 l.right 和r.left比较
return isSame(root.left,root.right);
};
队列解法图解思路
队列解法动画
队列解法代码
Java
public boolean isSymmetric(TreeNode root) {
//队列
Queue<TreeNode> queue = new LinkedList<>();
if (root == null)
return true;
//左子节点和右子节点同时入队
queue.add(root.left);
queue.add(root.right);
//如果队列不为空就继续循环
while (!queue.isEmpty()) {
//每两个出队
TreeNode left = queue.poll(), right = queue.poll();
//如果都为空继续循环
if (left == null && right == null)
continue;
//如果一个为空一个不为空,说明不是对称的,直接返回false
if (left == null ^ right == null)
return false;
//如果这两个值不相同,也不是对称的,直接返回false
if (left.val != right.val)
return false;
//这里要记住入队的顺序,他会每两个两个的出队。
//左子节点的左子节点和右子节点的右子节点同时
//入队,因为他俩会同时比较。
//左子节点的右子节点和右子节点的左子节点同时入队,
//因为他俩会同时比较
queue.add(left.left);
queue.add(right.right);
queue.add(left.right);
queue.add(right.left);
}
return true;
}
Go
func isSymmetric(root *TreeNode) bool {
u, v := root, root
q := []*TreeNode{}
q = append(q, u)
q = append(q, v)
for len(q) > 0 {
u, v = q[0], q[1]
q = q[2:]
if u == nil && v == nil {
continue
}
if u == nil || v == nil {
return false
}
if u.Val != v.Val {
return false
}
q = append(q, u.Left)
q = append(q, v.Right)
q = append(q, u.Right)
q = append(q, v.Left)
}
return true
}
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;
}
};
JS
var isSymmetric = function (root) {
if (!root) return true;
let queue = [root.left, root.right];
while (queue.length) {
const left = queue.pop();
const right = queue.pop();
if (left && right) {
if (left.val !== right.val) return false;
queue.push(left.left);
queue.push(right.right);
queue.push(left.right);
queue.push(right.left);
} else if (left || right) {
return false;
}
}
return true;
};
Python
class Solution(object):
def isSymmetric(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if not root or not (root.left or root.right):
return True
# 用队列保存节点
queue = [root.left,root.right]
while queue:
# 从队列中取出两个节点,再比较这两个节点
left = queue.pop(0)
right = queue.pop(0)
# 如果两个节点都为空就继续循环,两者有一个为空就返回false
if not (left or right):
continue
if not (left and right):
return False
if left.val!=right.val:
return False
# 将左节点的左孩子, 右节点的右孩子放入队列
queue.append(left.left)
queue.append(right.right)
# 将左节点的右孩子,右节点的左孩子放入队列
queue.append(left.right)
queue.append(right.left)
return True
最后
能看到这里的,都是小夕的真爱粉,顺手点个在看、赞、分享一下到朋友圈支持支持小夕! 这篇文章花了不少时间,希望阅读人数能多一些,小夕也很开心~~~毕竟辛辛苦苦写出来的东西,还是希望能被很多人看到,也是一种成就感吧~
对了,小夕目前是牛客网的合作作者了,大家帮忙点击一下阅读原文,在牛客网的帖子顶一下贴!回复个 支持小夕 就好~~~
由于动画、图解、漫画的形式很花时间,这篇题解的代码实现里有两位小夕的粉丝提供了 JS、Go语言的实现,感谢支持,小夕主要对Java比较熟,思路图解知道了语言熟悉的还是写起来比较快的,写其它语言的题解还是得上网搜一下语法,花的时间比较多,目前还差Python、C++、PHP的支持,如果有空的可以加小夕微信 tiehanhan12342 私聊我哦,哈哈哈。感谢两位的支持!!!