101. 对称二叉树
题意:给定一个二叉树,检查它是否是镜像对称的。
1、思路:递归法
-
判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!
-
本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的**内侧节点和外侧节点**是否相等。
-
因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中
-
既然确定了本次采用后续遍历,且一个树采用左右中,另一个树采用右左中
-
确定递归函数的参数和返回值
因为我们需要判断根节点的两个子树是否可以翻转,所以参数为根节点的左子树和右子树boolean isSymmetric(TreeNode left, TreeNode right)
-
确定终止条件
- 如果左节点为空,右节点不为空,则直接返回false
- 如果右节点为空,左节点不为空,则直接返回false
- 如果左右节点都为空,则直接返回true
- 如果左右节点都不为空,且左右节点值不相等,则直接返回false
- 如果左右节点都不为空,且左右节点值都相等,则继续向下执行,注意左右节点都为空,则直接返回true
-
确定单次循环逻辑
- 比较外侧是否对称,即左子树的左节点,右子树的右节点
- 比较内测是否对称,即左子树的右节点,右子树的左节点
- 内外侧都对称,则返回true
-
package com.yzu.lee.treenode;
import java.util.LinkedList;
import java.util.Queue;
/**
* @ClassName: IsSymmetric
* @Description:
* @author: Leekuangyew
* @date: 2022/5/27 9:51
*/
public class IsSymmetric {
public boolean isSymmetric(TreeNode root) {
//递归法
if (root == null) return false;
return isSymmetric(root.left, root.right);
}
private boolean isSymmetric(TreeNode left, TreeNode right) {
if (left == null && right != null) {
return false;
}
if (right == null && left != null) {
return false;
}
if (left == null && right == null) {
return true;
}
if (left.val != right.val) {
return false;
}
//为什么left和right都不为空,并且left和right的值相等,不直接相反 ,因为需要继续往下递归
boolean outer = isSymmetric(left.left, right.right); //左子树:左、 右子树:右
boolean inner = isSymmetric(left.right, right.left);//左子树:右、右子树:左
return outer && inner;
}
}
2、思路:迭代法
- 用队列实现层次遍历
- 创建一个队列,并将根节点的左节点和右节点分别加入队列中
- 循环判断队列是否为空
- 从队列中头部取出两个节点,分别赋值给左节点leftNode和右节点rightNode
- 对左右节点进行判断
- 如果左节点为空,右节点不为空,则直接返回false
- 如果右节点为空,左节点不为空,则直接返回false
- 如果左右节点都为空,则跳出本次循环,继续下次循环
- 如果左右节点都不为空,且左右节点值不相等,则直接返回false
- 接下来向队列中添加节点,注意顺序
- 添加左子树的左节点,添加右子树的右节点
- 添加左子树的右节点,添加右子树的左节点
- 最终返回true
public boolean isSymmetric(TreeNode root) {
//迭代法
if (root == null) return false;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root.left);
queue.offer(root.right);
while (!queue.isEmpty()) {
TreeNode leftNode = queue.poll();
TreeNode rightNode = queue.poll();
if (leftNode == null && rightNode != null) {
return false;
}
if (rightNode == null && leftNode != null) {
return false;
}
if (rightNode == null && leftNode == null) {
continue;
}
if (rightNode.val != leftNode.val) {
return false;
}
queue.offer(leftNode.left);
queue.offer(rightNode.right);
queue.offer(leftNode.right);
queue.offer(rightNode.left);
}
return true;
}
100.相同的树
题意:给你两棵二叉树的根节点 p
和 q
,编写一个函数来检验这两棵树是否相同。
如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
1、思路:递归法
- 本题与对称二叉树的方法基本相同
- 采用后续遍历,遍历两个树的遍历顺序是左右中
- 确定递归函数的参数和返回值,
boolean isSameTree(TreeNode p, TreeNode q)
,参数传入两个树的根节点 - 确定终止条件
- 如果左树节点为空,右树节点不为空,则直接返回false
- 如果右树节点为空,左树节点不为空,则直接返回false
- 如果左树节点和右树节点都为空,则直接返回true
- 如果左树节点和右树节点的值不想等,则直接返回false
- 确定单次循环逻辑
- 比较两棵树的左子树是否相等
- 比较两棵树的右子树是否相等
- 左右子树都相等,则返回true
- 确定递归函数的参数和返回值,
public boolean isSameTree(TreeNode p, TreeNode q) {
//递归法
if (p == null && q != null) {
return false;
}
if (q == null && p != null) {
return false;
}
if (q == null && p == null) {
return true;
}
if (q.val != p.val) {
return false;
}
boolean leftNode = isSameTree(p.left, q.left);
boolean rightNode = isSameTree(p.right, q.right);
return leftNode && rightNode;
}
2、思路:迭代法
- 利用队列层序遍历
- 一开始将左树根节点和右树根节点添加到队列中
- 当队列不为空时,就一直循环
- 从队列中取出两个节点,分别赋值为leftNode和rightNode
- 当左节点和右节点都为空,则跳出循环,并继续下次循环
- 当左节点为空或者右节点为空或者左节点与右节点值不想等,直接返回false
- 然后依次将左树和右树的左节点,左树和右树的右节点添加入队列中
public boolean isSameTree(TreeNode p, TreeNode q) {
//迭代法
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(p);
queue.offer(q);
while (!queue.isEmpty()) {
TreeNode leftNode = queue.poll();
TreeNode rightNode = queue.poll();
if (leftNode == null && rightNode == null) {
continue;
}
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
queue.offer(leftNode.left);
queue.offer(rightNode.left);
queue.offer(leftNode.right);
queue.offer(rightNode.right);
}
return true;
}
572.另一个树的子树
题意:给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
1、思路:递归法
- 按照后序进行遍历,两个后序遍历
- 确定递归函数的参数和返回值,
boolean isSubtree(TreeNode root, TreeNode subRoot)
,参数是树的根节点和子树的根节点 - 确定终止条件,当传入的节点为空时,直接返回false
- 确定单次递归逻辑
- 判断传入的树节点与子树是否相等,如果相等就直接返回true,
if (sameTree(root, subRoot))
- 判断两个树是否相等的方法在上一题中已经讲解
- 分别遍历左子树与右子树是否与子树相等,最终返回
left || right
- 判断传入的树节点与子树是否相等,如果相等就直接返回true,
- 确定递归函数的参数和返回值,
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
//递归法
if (root == null) {
return false;
}
if (sameTree(root, subRoot)) {
return true;
}
boolean left = isSubtree(root.left, subRoot);
boolean right = isSubtree(root.right, subRoot);
return left || right;
}
private boolean sameTree(TreeNode p, TreeNode q) {
if (p == null && q != null) {
return false;
}
if (q == null && p != null) {
return false;
}
if (q == null && p == null) {
return true;
}
if (q.val != p.val) {
return false;
}
boolean leftNode = sameTree(p.left, q.left);
boolean rightNode = sameTree(p.right, q.right);
return leftNode && rightNode;
}
2、思路:迭代法
- 迭代法与递归法思路完全一样
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
//迭代法
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
if (sameTree(node, subRoot)) {
return true;
}
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return false;
}
public boolean sameTree(TreeNode p, TreeNode q) {
//迭代法
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(p);
queue.offer(q);
while (!queue.isEmpty()) {
TreeNode leftNode = queue.poll();
TreeNode rightNode = queue.poll();
if (leftNode == null && rightNode == null) {
continue;
}
if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
return false;
}
queue.offer(leftNode.left);
queue.offer(rightNode.left);
queue.offer(leftNode.right);
queue.offer(rightNode.right);
}
return true;
}
104.二叉树的最大深度
题意:给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
说明: 叶子节点是指没有子节点的节点。
1、思路:递归法
- 本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。而根节点的高度就是二叉树的最大深度,所以本题中我们通过后序求的根节点高度来求的二叉树最大深度。
- 确定递归函数的参数和返回值:
int maxDepth(TreeNode root)
,参数是传入树的根节点,返回这棵树的深度 - 确定终止条件:如果为空节点的话,就返回0,表示高度为0。
if (root == null) return 0;
- 确定单层递归的逻辑:先求出左子树的深度,然后求出右子树的深度,最终返回左子树深度和右子树深度的较大值+1;
- 确定递归函数的参数和返回值:
public int maxDepth(TreeNode root) {
//DFS 递归
if (root == null) return 0;
else {
int leftHeight = maxDepth(root.left);
int rightHeight = maxDepth(root.right);
return Math.max(leftHeight, rightHeight) + 1;
}
}
2、思路:迭代法
- 使用迭代法的话,层序遍历最好,因为最大深度就是二叉树的层数
- 利用队列来模拟层序遍历
- 当队列不为空时,一直循环,每次循环深度加1
- 最后返回深度
/**
* 迭代法,使用层序遍历
*/
public int maxdepth(treenode root) {
if(root == null) {
return 0;
}
deque<treenode> deque = new linkedlist<>();
deque.offer(root);
int depth = 0;
while (!deque.isempty()) {
int size = deque.size();
depth++;
for (int i = 0; i < size; i++) {
treenode poll = deque.poll();
if (poll.left != null) {
deque.offer(poll.left);
}
if (poll.right != null) {
deque.offer(poll.right);
}
}
}
return depth;
}
559.n叉树的最大深度
题意:给定一个 n 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
1、思路:递归法
- 确定递归函数的参数和返回值:
int maxDepth(TreeNode root)
,参数是传入树的根节点,返回这棵树的深度 - 确定终止条件:如果为空节点的话,就返回0,表示高度为0。
if (root == null) return 0;
- 确定单层递归的逻辑:循环遍历每一个子树,然后求出每一子树的深度,并进行比较,最终返回最大的子树深度+1;
package com.yzu.lee.treenode;
import java.util.LinkedList;
import java.util.Queue;
/**
* @ClassName: MaxDepthN
* @Description:给定一个 N 叉树,找到其最大深度。
* 最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
* @author: Leekuangyew
* @date: 2022/5/27 12:01
*/
public class MaxDepthN {
public int maxDepth(Node root) {
//递归法
if (root == null) return 0;
int depth = 0;
for (Node child : root.children) {
depth = Math.max(maxDepth(child), depth);
}
return depth + 1;
}
}
2、思路:迭代法
- 依然是层序遍历
public int maxDepth(Node root) {
//迭代法
if (root == null) return 0;
int depth = 0;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
Node node = queue.poll();
if (node.children != null) {
for (Node child : node.children) {
queue.offer(child);
}
}
}
depth++;
}
return depth;
}
111.二叉树的最小深度
题意:给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明: 叶子节点是指没有子节点的节点。
思路:
直觉上好像和求最大深度差不多,其实还是差不少的。
遍历顺序上依然是**后序遍历(因为要比较递归返回之后的结果),但在处理中间节点的逻辑上,最大深度很容易理解**,最小深度可有一个误区,如图:
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。,注意是叶子节点。
1、思路:递归法
- 遍历顺序上依然是**后序遍历(因为要比较递归返回之后的结果)**
- 确定递归函数的参数和返回值
int minDepth(TreeNode root)
,参数为树的根节点,返回最小深度 - 确定终止条件
if (node == NULL) return 0;
终止条件也是遇到空节点返回0,表示当前节点的高度为0。 - 确定单层递归的逻辑
- 如果当前节点的左子树为空,右子树不为空,则返回右子树的深度+1;
- 如果当前节点的右子树为空,左子树不为空,则返回左子树的深度+1;
- 如果左右子树都不为空,则返回左右子树的最小深度+1;
- 确定递归函数的参数和返回值
package com.yzu.lee.treenode;
import java.time.OffsetDateTime;
/**
* @ClassName: MinDepth
* @Description:给定一个二叉树,找出其最小深度。 最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
* @author: Leekuangyew
* @date: 2022/5/25 22:09
*/
public class MinDepth {
/**
* 递归法,相比求MaxDepth要复杂点
* 因为最小深度是从根节点到最近**叶子节点**的最短路径上的节点数量
*/
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = minDepth(root.left);
int rightDepth = minDepth(root.right);
if (root.left == null) {
return rightDepth + 1;
}
if (root.right == null) {
return leftDepth + 1;
}
// 左右结点都不为null
return Math.min(leftDepth, rightDepth) + 1;
}
}
2、思路:迭代法
- 同样采用层序遍历
- 创建一个队列,并放入根节点
- 当队列不为空时,则一直循环,每次循环深度+1;
- 当某一个节点的左右子树都为空,则为叶子节点,直接返回深度
/**
* 迭代法,层序遍历
*/
public int minDepth(TreeNode root) {
if (root == null) {
return 0;
}
Deque<TreeNode> deque = new LinkedList<>();
deque.offer(root);
int depth = 0;
while (!deque.isEmpty()) {
int size = deque.size();
depth++;
for (int i = 0; i < size; i++) {
TreeNode poll = deque.poll();
if (poll.left == null && poll.right == null) {
// 是叶子结点,直接返回depth,因为从上往下遍历,所以该值就是最小值
return depth;
}
if (poll.left != null) {
deque.offer(poll.left);
}
if (poll.right != null) {
deque.offer(poll.right);
}
}
}
return depth;
}
222.完全二叉树的节点个数
题意:给出一个完全二叉树,求出该树的节点个数。
示例 1:
- 输入:root = [1,2,3,4,5,6]
- 输出:6
1、思路:递归法(普通二叉树)
- 递归遍历的顺序依然是后序(左右中)
- 确定递归函数的参数和返回值
int countNodes(TreeNode root)
,参数传入根节点,返回节点数 - 确定终止条件:
if (root == null) return 0;
- 确定单次递归逻辑:先求左子树的节点数量,再求右子树的节点数量,最终取总和再加1
- 确定递归函数的参数和返回值
public int countNodes(TreeNode root) {
//普通递归法
if (root == null) return 0;
int left = countNodes(root.left);
int right = countNodes(root.right);
return left + right + 1;
}
2、思路:迭代法(普通二叉树)
- 层序遍历
public int countNodes(TreeNode root) {
//普通迭代法
if (root == null) return 0;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
int result = 0;
while (!queue.isEmpty()) {
int size = queue.size();
while (size-- > 0) {
TreeNode node = queue.poll();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
result++;
}
}
return result;
}
3、思路:递归法(完全二叉树)
/**
* 针对完全二叉树的解法
* 满二叉树的结点数为:2^depth - 1
*/
public int countNodes(TreeNode root) {
if (root == null) {
return 0;
}
int leftDepth = getDepth(root.left);
int rightDepth = getDepth(root.right);
if (leftDepth == rightDepth) {// 左子树是满二叉树
// 2^leftDepth其实是 (2^leftDepth - 1) + 1 ,左子树 + 根结点
return (1 << leftDepth) + countNodes(root.right);
} else {// 右子树是满二叉树
return (1 << rightDepth) + countNodes(root.left);
}
}
private int getDepth(TreeNode root) {
int depth = 0;
while (root != null) {
root = root.left;
depth++;
}
return depth;
}
110.平衡二叉树
题意:给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
1、思路:递归法
-
看这道题目和104.二叉树的最大深度很像,其实有很大区别。
-
这里强调一波概念:
- 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
- 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
但leetcode中强调的深度和高度很明显是按照节点来计算的,如图:
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
为什么104.二叉树的最大深度 (opens new window)中求的是二叉树的最大深度,也用的是后序遍历。
那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这棵树的最大深度,所以才可以使用后序遍历。
-
所以这道题使用后序递归
- 明确递归函数的参数和返回值
int getHeight(TreeNode root)
,参数是传入节点,返回当前节点的高度 - 明确终止条件
if (root == null) return 0;
递归过程中遇到空节点,则返回0 - 确定单次递归的逻辑,分别求出左子树和右子树的高度,如果他们的差值小于等于1,则返回当前二叉树的高度,否则返回-1,已经不是平衡二叉树了
- 明确递归函数的参数和返回值
package com.yzu.lee.treenode;
import java.util.LinkedList;
import java.util.Queue;
/**
* @ClassName: IsBalanced
* @Description:给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。
* @author: Leekuangyew
* @date: 2022/5/27 18:00
*/
public class IsBalanced {
public boolean isBalanced(TreeNode root) {
//递归法 二叉树高度计算 使用后序
return getHeight(root) != -1;
}
private int getHeight(TreeNode root) {
if (root == null) return 0;
//左
int leftHeight = getHeight(root.left);
if (leftHeight == -1) {
return -1;
}
//右
int rightHeight = getHeight(root.right);
if (rightHeight == -1) {
return -1;
}
//中
if (Math.abs(rightHeight - leftHeight) > 1) {
return -1;
}
return Math.max(rightHeight, leftHeight) + 1;
}
}
257. 二叉树的所有路径
题意:给定一个二叉树,返回所有从根节点到叶子节点的路径。
说明: 叶子节点是指没有子节点的节点。
1、思路:递归法
- 要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。
- 在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。
- 前序遍历以及回溯的过程如图:
-
递归函数函数参数以及返回值:
void traversal(TreeNode root, List<Integer> paths, List<String> result)
,参数需要传入根节点,记录每一条路径,以及存放结果集的result -
确定递归终止条件
-
本题需要判断当前节点是否为叶子节点,如果是则停止,即**当 cur不为空,其左右孩子都为空的时候,就找到叶子节点。**
if (root.left == null && root.right == null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < paths.size() - 1; i++) { sb.append(paths.get(i) + "->"); } sb.append(paths.get(paths.size() - 1)); result.add(sb.toString()); return; }
-
确定单层递归逻辑
- 在递归一开始,需要把当前节点放入到Paths集合中
- 如果当前节点 的左节点不为空,则递归,递归完之后需要进行回溯,回溯和递归是一一对应的,有一个递归,就要有一个回溯
- 如果当前节点 的右节点不为空,则递归,递归完之后需要进行回溯,回溯和递归是一一对应的,有一个递归,就要有一个回溯
-
package com.yzu.lee.treenode;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName: BinaryTreePaths
* @Description:
* @author: Leekuangyew
* @date: 2022/5/27 18:30
*/
public class BinaryTreePaths {
public List<String> binaryTreePaths(TreeNode root) {
List<String> result = new ArrayList<>();
//递归+回溯
if (root == null) return result;
List<Integer> paths = new ArrayList<>();
traversal(root, paths, result);
return result;
}
private void traversal(TreeNode root, List<Integer> paths, List<String> result) {
//中
paths.add(root.val);
if (root.left == null && root.right == null) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < paths.size() - 1; i++) {
sb.append(paths.get(i) + "->");
}
sb.append(paths.get(paths.size() - 1));
result.add(sb.toString());
return;
}
//左
if (root.left != null) {
traversal(root.left, paths, result);
paths.remove(paths.size() - 1);
}
//右
if (root.right != null) {
traversal(root.right, paths, result);
paths.remove(paths.size() - 1);
}
return;
}
}