一道快手面试题,击败了100%用户

题目

出自牛客网题霸系列
链接: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才是对称的。

递归解法图解思路

递归解法动画

递归解法动画.Gif

递归解法代码

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 私聊我哦,哈哈哈。感谢两位的支持!!!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值