目录
[一、101 对称二叉树](#一、101 对称二叉树)
[二、100 相同的树](#二、100 相同的树)
[三、111 二叉树的最小深度](#三、111 二叉树的最小深度)
[四、226 翻转二叉树](#四、226 翻转二叉树)
一、101 对称二叉树
题目
给你一个二叉树的根节点 root
, 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
提示:
树中节点数目在范围 [1, 1000] 内
-100 <= Node.val <= 100
题解
方法一:递归(推荐)
乍一看无从下手,但用递归其实很好解决。
因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。返回值自然是bool类型。
根据题目的描述,镜像对称,就是左右两边相等,也就是左子树和右子树是相当的。注意这句话,左子树和右子相等,也就是说要递归的比较左子树和右子树。
我们将根节点的左子树记做 left,右子树记做 right。比较 left 是否等于 right,不等的话直接返回就可以了。如果相当,比较 left 的左节点和 right 的右节点,再比较 left 的右节点和 right 的左节点。
比如看下面这两个子树(他们分别是根节点的左子树和右子树),能观察到这么一个规律:
- 左子树的左孩子 == 右子树的右孩子
- 左子树的右孩子 == 右子树的左孩子
(1)确定终止条件
要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。
节点为空的情况有:(注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点)
- 左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:左右都不为空,比较节点数值,不相同就return false。
此时左右节点不为空,且数值也不相同的情况我们也处理了。根据上面信息可以总结出递归函数的两个终止条件:
- left 和 right 不等,或者 left 和 right 都为空
- 递归的比较 left.left 和 right.right,递归比较left.right 和 right.left
(2)确定单层递归的逻辑
此时才进入单层递归的逻辑,单层递归的逻辑就是处理 左右节点都不为空,且数值相同的情况。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
(3)代码如下,
java
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) {
return true; // 如果根节点为null,即空树,视为对称二叉树,返回true
}
return isMirror(root.left, root.right); // 调用isMirror方法判断左子树和右子树是否对称
}
private boolean isMirror(TreeNode left, TreeNode right) {
if (left == null && right == null) {
return true; // 如果左子树和右子树都为null,也视为对称,返回true
}
if (left == null || right == null) {
return false; // 如果左子树和右子树只有一个为null,视为不对称,返回false
}
return left.val == right.val && isMirror(left.left, right.right) && isMirror(left.right, right.left);
// 如果左子树和右子树的值相等,且同时满足左子树的左子树和右子树的右子树对称,
// 以及左子树的右子树和右子树的左子树对称,则视为对称,返回true;否则,返回false
}
}
isSymmetric方法是该函数的入口,接收一个二叉树的根节点作为参数。
- 首先判断根节点是否为null,如果是,即空树,视为对称二叉树,返回true。否则,调用isMirror 方法来判断左子树和右子树是否对称。
isMirror方法是递归判断左右子树是否对称的函数。
- 首先判断左子树和右子树是否都为null,如果是,即均为空树,视为对称,返回true。
- 然后判断左子树和右子树中只有一个为null的情况,即一个为空树一个不为空树,视为不对称,返回false。
- 最后,判断左子树的值和右子树的值是否相等,并且同时递归判断左子树的左子树和右子树的右子树是否对称,以及递归判断左子树的右子树和右子树的左子树是否对称。只有全部满足才视为对称,返回true;否则,返回false。
时间复杂度是O(n),因为我们遍历整个输入树一次,所以总的运行时间为 O(n),其中 n 是树中结点的总数。
空间复杂度:递归调用的次数受树的高度限制。在最糟糕情况下,树是线性的,其高度为 O(n)。因此,在最糟糕的情况下,由栈上的递归调用造成的空间复杂度为 O(n)。
方法二:迭代
这道题目我们也可以使用迭代法,使用队列来比较两个树(根节点的左右子树)是否相互翻转,(注意这不是层序遍历)。
回想下递归的实现:当两个子树的根节点相等时,就比较:左子树的 left 和 右子树的 right,这个比较是用递归实现的。
现在我们改用队列来实现,把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。思路如下:
- 首先从队列中拿出两个节点(left 和 right)比较:
- 将 left 的 left 节点和 right 的 right 节点放入队列
- 将 left 的 right 节点和 right 的 left 节点放入队列
代码如下,
java
public static boolean isSymmetric(TreeNode root){
Deque<TreeNode> deque = new LinkedList<>();
deque.offerFirst(root.left);
deque.offerLast(root.right);
while (!deque.isEmpty()) {
TreeNode leftNode = deque.pollFirst();
TreeNode rightNode = deque.pollLast();
if (leftNode == null && rightNode == null) {
continue;
}
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
deque.offerFirst(leftNode.left);
deque.offerFirst(leftNode.right);
deque.offerLast(rightNode.right);
deque.offerLast(rightNode.left);
}
return true;
}
使用双端队列 Deque
来存储需要比较的节点。将根节点的左子节点和右子节点添加到队列中。
当队列不为空时,进入循环。从队列中取出两个节点 left
和 right
进行比较。注意,这里使用 poll
方法每次从队列中取出两个节点。
如果两个节点都为空,则继续下一次循环,因为两个空节点是对称的。如果一个节点为空或者两个节点的值不同,则树不对称,返回 false
。
如果两个节点相等且不为空,将 left
节点的左子节点和 right
节点的右子节点加入队列的前端。将 left
节点的右子节点和 right
节点的左子节点加入队列的末端。这种方式确保了每次都比较成对的子节点,以保持对称性。
二、100 相同的树
题目
给你两棵二叉树的根节点 p
和 q
,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
提示:
两棵树上的节点数目都在范围 [0, 100] 内
-104 <= Node.val <= 104
题解
题意讲的很清楚,就是判断两颗二叉树是否完全相同,其实就可以梳理成以下三点:
- p 树的根节点和 q 树的根节点比较。
- p 树的左子树和 q 树的左子树比较。
- p 树的右子树和 q 树的右子树比较。
方法一:递归法
具体算法代码实现如下:
java
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
// 皆空,直接返回
if(p == null && q == null) return true;
// 如果其中一棵树为空,另一棵不为空,则一定不相同
if((p == null && q != null) || (p != null && q == null)) return false;
// 如果两棵树皆不为空,但是根节点的值不同,则一定不相同。
if(p.val !=q.val) return false;
//排除以上特殊情况,只需要都为true则说明两树完全相同。
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
时间复杂度:O(min(n,m))。其中 m 和 n 分别是两个二叉树的节点数。对两个二叉树同时进行深度优先搜索,只有当两个二叉树中的对应节点都不为空时才会访问到该节点,因此被访问到的节点数不会超过较小的二叉树的节点数。
空间复杂度:O(min(m,n))。其中 m 和 n 分别是两个二叉树的节点数。空间复杂度取决于递归调用的层数,递归调用的层数不会超过较小的二叉树的最大高度,最坏情况下,二叉树的高度等于节点数。
方法二:深度优先搜索
具体算法代码实现如下:
java
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
//两数都为空,直接返回
if (p == null && q == null) {
return true;
//存在一方为空,则不可能相同
} else if (p == null || q == null) {
return false;
} else if (p.val != q.val) {
return false;
} else {
return isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
}
}
时间复杂度:O(min(m,n))。其中 m 和 n 分别是两个二叉树的节点数。对两个二叉树同时进行深度优先搜索,只有当两个二叉树中的对应节点都不为空时才会访问到该节点,因此被访问到的节点数不会超过较小的二叉树的节点数。
空间复杂度:O(min(m,n))。其中 m 和 n 分别是两个二叉树的节点数。空间复杂度取决于递归调用的层数,递归调用的层数不会超过较小的二叉树的最大高度,最坏情况下,二叉树的高度等于节点数。
其实以上两种思路本质上都差不多的,唯独就是解题思路方向上细致不太一样。都是是运用递归思想,连枚举方式都是一样的,先枚举排除特殊性,然后再递归对比左右子数是否相等。
三、111 二叉树的最小深度
题目
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
java
说明:叶子节点是指没有子节点的节点
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
题解
方法一:递归法
递归计算每个结点的最小深度
- 当当前节点是空,直接返回
- 当左子树是空,且右子树不为空,则返回 右子树的最小深度 + 1
- 当右子树是空,且左子树不为空,则返回 左子树的最小深度 + 1
- 当左右子树均不为空,则返回 左右子树的最小深度的最小值 + 1
时间复杂度 O(n),n 为二叉树的节点数,需要遍历二叉树的所有节点。
java
class Solution {
public int minDepth(TreeNode root) {
if(root == null) return 0;
int l = minDepth(root.left);
int r = minDepth(root.right);
if(root.left == null && root.right != null) return 1 + r;
if(root.right == null && root.left != null) return 1 + l;
return 1 + Math.min(l, r);
}
}
四、226 翻转二叉树
题目
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
java
提示:
树中节点数目范围在 [0, 100] 内
-100 <= Node.val <= 100
题解
翻转一棵二叉树意味着交换每个节点的左右子树。我们可以使用递归的方法,从根节点开始,对每个节点进行如下操作:
- 交换当前节点的左右子树
- 递归地翻转当前节点的左子树
- 递归地翻转当前节点的右子树
递归的终止条件是当前节点为 null,即叶子节点。
代码如下,
java
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null) return root;
TreeNode temp = root.left;
root.left = root.right;
root.right= temp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
递归函数需要访问每个节点恰好一次,因此时间复杂度为 O(n),其中 n 是树中节点的数量。
递归函数在递归过程中使用的栈空间的最大深度等于树的高度,最坏情况下,树是一个链表结构,高度为 n,因此空间复杂度为 O(n)。