hot100_101. 对称二叉树
hot100_101. 对称二叉树
- 思路
- 方法一:递归
- 迭代
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
思路
方法一:递归
如果一个树的左子树与右子树镜像对称,那么这个树是对称的。因此,该问题可以转化为:两个树在什么情况下互为镜像?如果同时满足下面的条件,两个树互为镜像:
1.它们的两个根结点具有相同的值
2.每个树的右子树都与另一个树的左子树镜像对称
我们可以实现这样一个递归函数,通过「同步移动」两个指针的方法来遍历这棵树,p 指针和 q 指针一开始都指向这棵树的根,随后 p 右移时,q 左移,p 左移时,q 右移。每次检查当前 p 和 q 节点的值是否相等,如果相等再判断左右子树是否对称。
class Solution {
public boolean isSymmetric(TreeNode root) {
return check(root.left,root.right);
}
public boolean check(TreeNode q,TreeNode p) {
if(q==null && p==null){
return true;
}
//已经排除了q==null && p==null , 剩下的可能是 两个不会同时为null,但只要一个为null就false
if(q==null || p==null){
return false;
}
return q.val==p.val && check(q.right,p.left) && check(q.left,p.right);
}
}
迭代
首先我们引入一个队列,这是把递归程序改写成迭代程序的常用方法。
初始化时我们把根节点入队两次。每次提取两个结点并比较它们的值(队列中每两个连续的结点应该是相等的,而且它们的子树互为镜像),然后将两个结点的左右子结点按相反的顺序插入队列中。
当队列为空时,或者我们检测到树不对称(即从队列中取出两个不相等的连续结点)时,该算法结束。
class Solution {
public boolean isSymmetric(TreeNode root) {
return check(root,root);
}
public boolean check(TreeNode u,TreeNode v){
Queue<TreeNode> q = new LinkedList<TreeNode>();
q.offer(u);
q.offer(v);
while(!q.isEmpty()){
u = q.poll();
v = q.poll();
if(u==null && v==null){
continue;
}
if(v==null || u==null || (u.val != v.val)){
return false;
}
q.offer(u.left);
q.offer(v.right);
q.offer(u.right);
q.offer(v.left);
}
return true;
}
}
在逻辑表达式中,if(v == null || u == null || (u.val != v.val)) 和 if((u.val != v.val) || v == null || u == null) 在功能上是等价的,因为逻辑或运算符 || 是可交换的,即 A || B 和 B || A 的结果是相同的。然而,它们在代码的可读性和执行顺序上可能有所不同。
- 逻辑功能上的等价性
从逻辑上讲,这两个表达式是等价的。它们都检查以下条件:
v 是否为 null
u 是否为 null
如果 u 和 v 都不为 null,则检查 u.val 是否不等于 v.val
只要任意一个条件为 true,整个表达式的结果就是 true,进而触发 if 语句中的代码块。 - 执行顺序上的差异
虽然逻辑上等价,但实际执行顺序可能会受到 短路逻辑(Short-circuiting) 的影响。在 Java 中,|| 是短路逻辑运算符,即如果前面的条件已经能够确定整个表达式的结果,后面的条件就不会被计算。
第一种写法:
java复制
if(v == null || u == null || (u.val != v.val))
首先检查 v == null:
如果 v == null 为 true,整个表达式直接返回 true,不会继续检查后面的条件。
如果 v != null,则检查 u == null:
如果 u == null 为 true,整个表达式返回 true,不会继续检查后面的条件。
如果 u != null,则检查 u.val != v.val。
第二种写法:
java复制
if((u.val != v.val) || v == null || u == null)
首先检查 u.val != v.val:
如果 u.val != v.val 为 true,整个表达式直接返回 true,不会继续检查后面的条件。
如果 u.val == v.val,则检查 v == null:
如果 v == null 为 true,整个表达式返回 true,不会继续检查后面的条件。
如果 v != null,则检查 u == null。 - 可读性和潜在问题
虽然逻辑上等价,但第一种写法更符合常见的编程习惯,尤其是处理可能为 null 的对象时:
安全性:第一种写法先检查 v 和 u 是否为 null,避免了在 u 或 v 为 null 时直接访问 u.val 或 v.val,从而避免了潜在的 NullPointerException。
可读性:第一种写法更清晰地表达了意图,即先检查 null 条件,再检查值是否相等。 - 总结
虽然逻辑上等价,但两种写法在执行顺序和可读性上有差异。推荐使用第一种写法:
java复制
if(v == null || u == null || (u.val != v.val))
这种写法更安全,也更符合常见的编程习惯,能够有效避免 NullPointerException,并且逻辑更清晰。