前言
记录 LeetCode 刷题中遇到的二叉树相关题目,第五篇
剑指 Offer 26. 树的子结构
一共用到两个递归函数,一个是 isSub() 方法,用于判断 B 是否是 A 的子结构;一个是 isEqual() 方法,是比较 A 跟 B 是否相同,但当比较到B 为 null 的时候,也认为是相同的;相当于判断 B 是否是 A 的子结构,前提是 B 的根节点跟 A 的根节点重合
具体来说,调用 isSub() 方法判断 B 是不是 A 的子结构,如果此时 A 的值等于 B 的值,那就调用 isEqual() 判断以 A 为根的这部分是不是就是 B 在原来的 A 中的子结构;如果此时 A 的值不等于 B 的值,那就直接往 A 的左右子树上找 B 的子结构,即 isSub(A.left,B) || isSub(A.right,B)
再通俗一点,isSub() 方法就是在 A 上找出值跟 B 相等的节点,然后 调用 isEqual() 方法判断这个节点是不是 B 所对应的子结构,如果是就返回 true ,不是就继续找下一个值跟 B 的节点,直到 A 为 null 返回 false 或者找到 B 的子结构 返回 true
而 isEqual() 方法就是在判断两棵树是否相同的基础上,加上一个条件:如果 B 为 null,就返回 true
class Solution {
boolean isEqual(TreeNode A,TreeNode B){
if(B == null) return true;
if(A == null || A.val != B.val) return false;
return isEqual(A.left,B.left) && isEqual(A.right,B.right);
}
boolean isSub(TreeNode A,TreeNode B){
if(A == null) return false;
if(A.val == B.val){
if(isEqual(A.left,B.left) && isEqual(A.right,B.right)) return true;
}
return isSub(A.left,B) || isSub(A.right,B);
}
public boolean isSubStructure(TreeNode A, TreeNode B) {
//特例先判断,如果有一棵树为 null 直接返回 false
if(A == null || B == null) return false;
return isSub(A,B);
}
}
572. 另一棵树的子树
判断一棵树是否含有另一棵树的子树,首先先判断以当前这棵树是否就和另一棵树相同,不相同的话就往左右子树上找
所以这里还需要一个判断两棵树是否相同的方法 isThsSame()
class Solution {
public boolean isTheSame(TreeNode root,TreeNode subRoot){
if(root == null && subRoot == null) return true;
if((root == null && subRoot != null) || (root != null && subRoot == null)) return false;
if(root.val != subRoot.val) return false;
return isTheSame(root.left,subRoot.left) && isTheSame(root.right,subRoot.right);
}
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if(root == null && subRoot == null) return true;
if((root == null && subRoot != null) || (root != null && subRoot == null)) return false;
if(isTheSame(root,subRoot)) return true;
return isSubtree(root.left,subRoot) || isSubtree(root.right,subRoot);
}
}
100.相同的树
判断两棵树是否相同,先判断根是否相同,然后先判断左子树是否相同,如果左子树不相同,那就可以不用判断右子树是否相同直接返回 false,如果左子树也是相同的,那就返回右子树是否相同的结果
public boolean isSameTree(TreeNode p, TreeNode q) {
//防止下面.val和.left .right出现空指针异常,
//所有两棵树可能为null的情况都要先判断
if(p == null && q==null)return true;
if(p== null || q == null) return false;
if(p.val != q.val) return false;
boolean left = isSameTree(p.left,q.left);
if(left == false) return false;
return isSameTree(p.right,q.right);
}
101. 对称二叉树
如果 root 为空直接返回 true,否则,比较左儿子跟右儿子是否相等,如果相等,再比较 左儿子的左儿子 跟 右儿子的右儿子 以及 左儿子的右儿子 跟 右儿子的左儿子 是否相等…
以此类推,递归函数对两个对称的节点判断值是否相等,如果相等就判断左边节点的左儿子跟右边节点的右儿子是否相等,以及左边儿子的右儿子跟右边节点的左儿子是否相等,只有这两个关系都相等才能说明对称
class Solution {
public boolean rec(TreeNode l,TreeNode r){
if(l == null && r != null) return false;
if(l != null && r == null) return false;
if(l == null && r == null) return true;
if(l.val != r.val) return false;
return rec(l.left,r.right) && rec(l.right,r.left);
}
public boolean isSymmetric(TreeNode root) {
if(root == null) return true;
return rec(root.left,root.right);
}
}
662. 二叉树最大宽度
首先,不能把宽度认为是每一层的节点总数,这里说的宽度只跟每一层的最左边节点到最右边节点之间的距离有关
基于深度优先遍历 (先序遍历),每个节点对应一个下标 pos (可以看成是使用数组顺序结构存储的二叉树),然后左儿子对应的就是 2 * pos,右儿子对应的就是 2 * pos + 1。遍历的过程中一边记录当前层数,那么各个层中,下标相差最大的那个距离就是所求的高度
由于不知道深度最多能到多少,所以不能使用数组只能使用一个 Map 保存各层最左边的节点的索引
由于是基于先序遍历,所以可以知道每层遍历到的第一个节点就是各层最左边的节点
记录下各层最左边的节点的索引,然后后面再访问到相同层数的节点的时候,就可以把这个节点的索引值减去最左边节点的索引值得到一个宽度,然后比较跟之前得到的最大宽度哪个更大即可
class Solution {
int ans;
Map<Integer, Integer> left;
public int widthOfBinaryTree(TreeNode root) {
ans = 0;
left = new HashMap();
dfs(root, 0, 1);
return ans;
}
//depth 维护当前遍历到的深度,pos 维护当前要访问的节点的下标
public void dfs(TreeNode root, int depth, int pos) {
if (root == null) return;
//记录最左边节点的索引。putIfAbsent 是指要存的 key不存在才放入这个键值对,这样就不会覆盖原有 key 的值
left.putIfAbsent(depth, pos);
//更新维护最大宽度。而且要前面先 put 了再来算这个宽度,否则,当遍历到的是每层的第一个节点时
//left调用get(depth)为null
ans = Math.max(ans, pos - left.get(depth) + 1);
dfs(root.left, depth + 1, 2 * pos);
dfs(root.right, depth + 1, 2 * pos + 1);
}
}
104.二叉树的最大深度
public int maxDepth(TreeNode root) {
if(root == null) return 0;
else return 1 + Math.max(maxDepth(root.left),maxDepth(root.right));
}
111.二叉树的最小深度
public int minDepth(TreeNode root) {
if(root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
//如果left为0,说明左儿子为空,如果此时right不为0,说明叶子节点应该在右子树上找
//也有可能left跟right都为0,那么当前节点就是叶子结点,return1
if(left == 0) return 1 + right;
if(right == 0) return 1 + left;
//如果left,right都不为0,说明左右子树都不为空,所以最小深度是两棵子树中最小深度的较小值
return 1 + Math.min(left,right);
}
513. 找树左下角的值
基于先序遍历,每一层的最左边的节点一定是那一层中第一个被访问到的节点。所以可以维护当前访问的节点的层数 layer,同时记录已经访问过的最大层数 maxLayer,只要当前 layer 大于 maxLayer,说明当前访问的节点就是第 layer 层的最左边节点,于是记录这个节点的值 val
以此类推,访问到最后,所记录的 val 就是最下层的最左边节点的值,也就是树左下角的值
class Solution {
int maxLayer = -1;
int val = -1;
public void rec(TreeNode root,int layer){
if(root == null) return;
if(layer > maxLayer){
val = root.val;
maxLayer = layer;
}
rec(root.left,layer + 1);
rec(root.right,layer + 1);
}
public int findBottomLeftValue(TreeNode root) {
rec(root,0);
return val;
}
}