主要介绍力扣 101. 对称二叉树一题的递归和迭代解法。并且通过这题的思想还可以练习100. 相同的树与力扣 572. 另一棵树的子树
1.递归参数和返回值
因为需要比较是否对称,因此每次递归传入的参数因当是一对对称位置上的结点,返回值应当是bool类型
boolean compare(TreeNode leftNode,TreeNode rightNode)
2.终止条件和递归过程
比较两个结点:
①首先应该先判断是否为空,都为空则对称,返回true;
②能经过①走到②这一步,则两个结点不是全为空,有三种情况,只有左为空,只有右为空,或均不为空;前两种情况说明不对称,返回false,均不为空情况下,如果结点值不同,也不对称,返回false;
//首先判断空节点的三种情况
if(leftNode==null&&rightNode==null)return true;
else if(leftNode!=null&&rightNode==null)return false;
else if(leftNode==null&&rightNode!=null)return false;
//判断空节点之后判断值是否相同
else if(leftNode.val!=rightNode.val)return false;
③能走到这一步,说明两个结点对称了,需要进一步判断下一层的结点的对称情况,也就是下一层的外侧和内侧的两对结点(需要同时满足才是对称,这也决定了迭代法里面每次都是比较一对结点,循环末尾需要内外侧两对结点成对入队)
//排除空节点和值之后,肯定leftNode.val=rightNode.val,递归判断下一层的情况
else return compare(leftNode.left,rightNode.right)&&compare(leftNode.right,rightNode.left);//分别判断下一层外侧和内侧结点是否对称
整体代码如下:
//递归方法
class Solution {
public boolean isSymmetric(TreeNode root) {
return compare(root.left,root.right);//这题结点树大于等于1,不用判断空节点
}
boolean compare(TreeNode leftNode,TreeNode rightNode){
//首先判断空节点的三种情况
if(leftNode==null&&rightNode==null)return true;
else if(leftNode!=null&&rightNode==null)return false;
else if(leftNode==null&&rightNode!=null)return false;
//判断空节点之后判断值是否相同
else if(leftNode.val!=rightNode.val)return false;
//排除空节点和值之后,肯定leftNode.val=rightNode.val,递归判断下一层的情况
else return compare(leftNode.left,rightNode.right)&&compare(leftNode.right,rightNode.left);//分别判断下一层外侧和内侧结点是否对称
}
}
每次都是从队中取出一对结点结点进行比较,一层层比较下去,具体过程见代码注释。
//迭代方法
class Solution {
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode>que=new LinkedList<>();//用队列暂存一对对结点
que.offer(root.left);//入队出队一定都是成双成对的
que.offer(root.right);
TreeNode leftNode,rightNode;//暂存出队的一对结点,用于比较
while(!que.isEmpty()){
leftNode=que.poll();
rightNode=que.poll();
//首先判断结点是否为空,当两个结点都为空,说明对称,进入下一次循环
if(leftNode==null&&rightNode==null)continue;
//能走到这里,说明两个结点不是全空,可能是其中一个为空,也可能是都不空(那就要判断值是不是相等)
if(leftNode==null||rightNode==null||leftNode.val!=rightNode.val)return false;
//能走到这里,说明这两个点对称了,要判断下一对结点(入队)
que.offer(leftNode.left);
que.offer(rightNode.right);
que.offer(leftNode.right);
que.offer(rightNode.left);
}
return true;//如果循环结束都没返回false,则说明全过程都是对称
}
}
理解了这题的做法,可以对另外三题进行练习:
原题链接
//递归(深度优先遍历)
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null&&q==null)return true;
else if(p==null||q==null||p.val!=q.val)return false;
//分别递归判断左子树和右子树
else return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);//注意这里是比较对应位置,而不是像上题一样的对称位置
}
}
//迭代法(广度优先遍历),用一个队列即可,每次出入队都成对
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
Queue<TreeNode>que=new LinkedList<>();
que.offer(p);que.offer(q);
while(!que.isEmpty()){
p=que.poll();q=que.poll();
if(p==null&&q==null)continue;
if(p==null||q==null||p.val!=q.val)return false;
que.offer(p.left);
que.offer(q.left);
que.offer(p.right);
que.offer(q.right);
}
return true;
}
}
原题链接
递归解法:需要理解100题的递归方法,这题递归的思路就会很清晰。
//递归解法
class Solution {
//100题的递归函数,直接使用,p对应母树root的点,q对应子树subRoot的点
public boolean isSameTree(TreeNode p, TreeNode q) {
if(p==null&&q==null)return true;
else if(p==null||q==null||p.val!=q.val)return false;
//分别递归判断母树和子树的左子树和右子树
else return isSameTree(p.left,q.left)&&isSameTree(p.right,q.right);
}
//主函数
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root==null)return false;//母树遍历完都没有返回true,说明都没匹配上
//每层递归里面调用判断两树相等函数,判断是否相等,再调用主函数递归判断母树中当前结点的左右子树;
return isSameTree(root,subRoot)||isSubtree(root.left,subRoot)||isSubtree(root.right,subRoot);
}
}
//哈希+深度优先遍历解法
class Solution {
//随便两个素数P、Q,用在哈希函数里面计算结点哈希值
int P=7,Q=11,mod=100007;//mod用于防止哈希计算过程数值溢出
boolean ans=false;//ans标记子树是否在母树中出现过
int T=-99;//用于记录子树的哈希值,一开始设置为负数是为了避免碰巧等于子树哈希值,这样就可以让主函数里的T=dfs(subRoot)语句,给T赋正确的哈希值
//深度优先遍历,并且计算每个结点对应子树的哈希值
public int dfs(TreeNode root){
//根节点如果是空的,随便返回一个值(注意不能返回0,因为结点值为0也可能返回0,这样就没办法区分结点值为0和结点为空的情况,结果会出错)
if(root==null)return 1234;
//root.val%mod+mod先把当前遍历到的点值映射成非负,最后%mod防止溢出
int x=(root.val%mod+mod)%mod;
int L=dfs(root.left);//求左子树的哈希值
int R=dfs(root.right);//右子树的哈希值
//若在深度优先遍历母树过程中,发现母树某结点对应子树的哈希值与待匹配子树相同,说明子树在母树中出现了,ans标记置true
if(T==L||T==R)ans=true;
//返回当前root结点(这一层递归传入的结点)对应子树的哈希值
return (x+L*P+R*Q)%mod;
}
//主函数
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
T=dfs(subRoot);//求子树的哈希值
//判断子树哈希值和母树根节点哈希值相同,若相同则标记,不相同则在深度优先遍历母树过程中继续判断
if(T==dfs(root))ans=true;
return ans;//最终返回标记
}
}
原题链接
这题要注意与力扣 572. 另一棵树的子树区分,这题的子树只要结构可以对应就可以,没有要求从根节点到叶子结点完全对应。
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
//约定空树不是任意一个树的子结构
if(A == null || B == null) return false;
// dfs(A, B) 当前节点B是否是A的子树,若不是,则同理判断当前节点的孩子节点
return dfs(A, B)||isSubStructure(A.left,B)||isSubStructure(A.right, B);
}
public boolean dfs(TreeNode A, TreeNode B){
//这里如果B为空,说明B已经访问完了,确定是A的子结构
if(B == null) return true;
//如果B不为空A为空,或者这两个节点值不同,说明B树不是A的子结构,直接返回false
if(A == null|| A.val != B.val ) return false;
// 若两个节点的值不同 则B不可能是A的子树 相同则比较两个节点的孩子节点是否相同
return dfs(A.left, B.left)&& dfs(A.right, B.right);
}
}