给定一个二叉树,检查它是否是镜像对称的。
例如,二叉树 [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
方法一:递归
二叉树的遍历问题最容易想到的就是递归,因为子树结构和整棵树都一样,可以用相同的方法递归遍历。
- 根结点为空,直接返回true
- 根结点不为空,比较左右子树。分三种情况:
- 左右子树都为空,返回true。
- 左右子树只有一个为空,返回false。
- 当左右子树都不为空,比较左右子树的根结点是否相同,相同则分别递归比较左子树的左子树和右子树的右子树,左子树的右子树和右子树的左子树。只有三者都为true 时,才返回true。
var isSymmetric = function(root) {
if(root == null) return true;
let isCommon = function(left,right){
if(left == null) return right == null;
if(right == null) return false;
return left.val == right.val && isCommon(left.left,right.right) && isCommon(left.right,right.left);
};
return isCommon(root.left,root.right);
};
复杂度分析
假设树上一共 n 个节点。
- 时间复杂度:这里遍历了这棵树,渐进时间复杂度为 O(n)。
- 空间复杂度:这里的空间复杂度和递归使用的栈空间有关,这里递归层数不超过n,故渐进空间复杂度为 O(n)。
方法二:迭代
首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。初始化时我们把左右子树的根结点入队。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。
- 根为空,返回true。
- 维护一个队列,初始化时把根节点(如果存在)的左右子树入列。
- 每次出队两个节点,比较是否对称。如果不对称,那整个树就不对称,结束BFS,如果对称,则下一对节点入列。
哪些情况不对称:
- 一个为 null 一个不为 null,直接返回false。
- 都存在,但 root 值不同,直接返回 false。
都为空时继续下一对节点的比较。直到队列为空遍历结束。对称时下一对节点入队的顺序根据二叉树的结构:左子树的左子树和右子树的右子树,左子树的右子树和右子树的左子树。
var isSymmetric = function(root) {
if(root == null) return true;
let queue = [];
queue.push(root.left,root.right);
while(queue.length){
let left = queue.shift();
let right = queue.shift();
if(left == null && right == null) continue;
if((left == null || right == null) || left.val !== right.val) {
return false;
}
queue.push(left.left,right.right,left.right,right.left);
}
return true;
};
复杂度分析
- 时间复杂度:O(n),同「方法一」。
- 空间复杂度:这里需要用一个队列来维护节点,每个节点最多进队一次,出队一次,队列中最多不会超过 n个点,故渐进空间复杂度为 O(n)。