文章目录
系列文章目录
一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的层序遍历及相关题目
更新中 … …
前言
在上边两篇文章熟练使用二叉树的递归遍历和层序遍历基础上,本文记录了关于二叉树的一些属性如:深度、高度、相同二叉树及子树、平衡二叉树、二叉树的路径,以及结点的属性的相关题目,在二叉树的所有路径中还简单提到了回溯。
刷题路线来自 :代码随想录
题录
101. 对称二叉树
Leetcode 链接
给你一个二叉树的根节点 root , 检查它是否轴对称。
示例 1:
输入:root = [1,2,2,3,4,4,3]
输出:true
示例 2:
输入:root = [1,2,2,null,3,null,3]
输出:false
题解:
方法一: 递归
class Solution {
public boolean isSymmetric(TreeNode root) {
return dfs(root.left, root.right);
}
public boolean dfs(TreeNode p, TreeNode q) {
// 先序遍历
if (p == null && q == null) return true;
// 肯定不都为null,一个为null时
if (p == null || q == null) return false;
// 不相等时
if (p.val != q.val) return false;
// 向左向右递归,只要有一个 false 就不会继续向下递归了
return dfs(p.left, q.right) && dfs(p.right, q.left);
}
}
方法二:迭代
同时从左从右遍历,使用两个队列记录各自的遍历结点并比较
注意: 这里队列是使用的链表结构,因为使用数组结构时,当孩子结点为空让孩子结点入队会出现异常
class Solution {
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode> queue1 = new LinkedList<>();
Queue<TreeNode> queue2 = new LinkedList<>();
queue1.offer(root.left);
queue2.offer(root.right);
while (!queue1.isEmpty() && !queue2.isEmpty()) {
// 弹出两个对称的结点
TreeNode node1 = queue1.poll();
TreeNode node2 = queue2.poll();
// 层序遍历,两头走到头为空,需要继续向中间走
if (node1 == null && node2 == null) continue;
// 不相等
if (node1 == null || node2 == null) return false;
if (node1.val != node2.val) return false;
// 两个队列分别让结点入栈
queue1.offer(node1.left);
queue1.offer(node1.right);
queue2.offer(node2.right);
queue2.offer(node2.left);
}
return (queue1.isEmpty() && queue2.isEmpty());
}
}
简化后:使用一个队列即可,保证连续出栈两个结点的对称结点,那么入栈的时候堆成位置一起入栈即可
class Solution {
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root.left);
queue.offer(root.right);
while (!queue.isEmpty()) {
TreeNode node1 = queue.poll();
TreeNode node2 = queue.poll();
if (node1 == null && node2 == null) continue;
if (node1 == null || node2 == null) return false;
if (node1.val != node2.val) return false;
// 对称的结点放在一起,出栈是连续的就可以判断是否相等
queue.offer(node1.left);
queue.offer(node2.right);
queue.offer(node1.right);
queue.offer(node2.left);
}
return true;
}
}
100. 相同的树
Leetcode 链接
给你两棵二叉树的根节点 p 和 q ,编写一个函数来检验这两棵树是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
示例 1:
输入:p = [1,2,3], q = [1,2,3]
输出:true
示例 2:
输入:p = [1,2], q = [1,null,2]
输出:false
示例 3:
输入:p = [1,2,1], q = [1,1,2]
输出:false
**题解:**同上题
class Solution {
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (p == null || q == null) return false;
return p.val == q.val && isSameTree(q.left, p.left) && isSameTree(p.right, q.right);
}
}
572. 另一棵树的子树
Leetcode 链接
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
示例 1:
输入:root = [3,4,5,1,2], subRoot = [4,1,2]
输出:true
示例 2:
输入:root = [3,4,5,1,2,null,null,null,null,0], subRoot = [4,1,2]
输出:false
题解:
方式一:暴力递归
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
// 先序遍历
if (root == null) return false;
// 如果与子树根节点相同,判断是否子树
if (root.val == subRoot.val && dfs(root, subRoot)) return true;
// 只有一颗相同的树就行
return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
// 判断两棵树是否相同
public boolean dfs(TreeNode p, TreeNode q) {
if (p == null && q == null) return true;
if (p == null || q == null) return false;
return p.val == q.val && dfs(p.left, q.left) && dfs(p.right, q.right);
}
}
104. 二叉树的最大深度
Leetcode 链接
给定一个二叉树,找出其最大深度。二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。说明: 叶子节点是指没有子节点的节点。
示例:
给定二叉树 [3,9,20,null,null,15,7],
返回它的最大深度 3 。
题解:
方式一:迭代法
层序遍二叉树,遍历完后返回层数
class Solution {
public int maxDepth(TreeNode root) {
int res = 0;
Queue<TreeNode> queue = new ArrayDeque<>();
if (root != null) queue.offer(root);
while(!queue.isEmpty()) {
int len = queue.size();
res++;
while (len-- > 0) {
TreeNode node = queue.poll();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return res;
}
}
方式二:递归
class Solution {
public int maxDepth(TreeNode root) {
return dfs(root);
}
public int dfs(TreeNode root) {
if (root == null) return 0;
// 返回左右子树最大深度 + 1(加一表示当前层)
return Math.max(dfs(root.left), dfs(root.right)) + 1;
}
}
111. 二叉树的最小深度
Leetcode 链接
给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。说明:叶子节点是指没有子节点的节点。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:2
示例 2:
输入:root = [2,null,3,null,4,null,5,null,6]
输出:5
题解:
方式一:迭代法
层序遍历找到第一个没有孩子的结点
class Solution {
public int minDepth(TreeNode root) {
int res = 0;
Queue<TreeNode> queue = new ArrayDeque<>();
if (root != null) queue.offer(root);
while(!queue.isEmpty()) {
int len = queue.size();
res++;
while (len-- > 0) {
TreeNode node = queue.poll();
// 左右孩子都为 null
if (node.left == null && node.right == null) return res;
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return res;
}
}
方式二:递归
要注意 [1, 2],最小深度为 2 ,而不是 0
class Solution {
public int minDepth(TreeNode root) {
return dfs(root);
}
public int dfs(TreeNode root) {
if (root == null) return 0;
// 左结点为 null 去递归右边,右结点为 null,去递归左边
if (root.left == null) return dfs(root.right) + 1;
if (root.right == null) return dfs(root.left) + 1;
return Math.min(dfs(root.left), dfs(root.right)) + 1;
}
}
222. 完全二叉树的节点个数
Leetcode 链接
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
示例 1:
输入:root = [1,2,3,4,5,6]
输出:6
示例 2:
输入:root = []
输出:0
示例 3:
输入:root = [1]
输出:1
题解: 直接递归就完事了
class Solution {
public int countNodes(TreeNode root) {
if (root == null) return 0;
// 左边结点数加上右边节点数加上当前结点
return countNodes(root.left) + countNodes(root.right) + 1;
}
}
110. 平衡二叉树
Leetcode 链接
给定一个二叉树,判断它是否是高度平衡的二叉树。本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
示例 1:
输入:root = [3,9,20,null,null,15,7]
输出:true
示例 2:
输入:root = [1,2,2,3,3,null,null,4,4]
输出:false
示例 3:
输入:root = []
输出:true
题解:
方法一:暴力递归
class Solution {
public boolean isBalanced(TreeNode root) {
// 中序遍历二叉树,每个结点进行判断左右子树差值
if (root == null) return true;
// 左右结点差值小于等于1,再去遍历左右结点
return Math.abs(dfs(root.left) - dfs(root.right)) <= 1 && isBalanced(root.left) && isBalanced(root.right);
}
// 求最大高度
public int dfs(TreeNode root) {
if (root == null) return 0;
return Math.max(dfs(root.left), dfs(root.right)) + 1;
}
}
方法二:优化递归
因为上边的方法每个结点都遍历了两次,第二次遍历为求做大高度差,
class Solution {
public boolean isBalanced(TreeNode root) {
return (dfs(root) != -1);
}
public int dfs(TreeNode root) {
if (root == null) return 0;
// 先得到左右子树高度差有,-1 表示存在高度差大于1
int leftHight = dfs(root.left);
if (leftHight == -1) {
return -1;
}
int rightHight = dfs(root.right);
if (rightHight == -1) {
return -1;
}
// 如果左右差大于 1,放返回-1
if (Math.abs(leftHight - rightHight) > 1) {
return -1;
}
return Math.max(dfs(root.left), dfs(root.right)) + 1;
}
}
257. 二叉树的所有路径
Leetcode 链接
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5]
输出:[“1->2->5”,“1->3”]
示例 2:
输入:root = [1]
输出:[“1”]
题解: 这里用到了回溯
- 先序遍历二叉树
- 使用列表记录遍历的路径
- 如果左右孩子结点都为 null,表示一条路径遍历结束,添加到返回列表中
- 回溯的时候去重列表中的结点
class Solution {
List<Integer> path = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
if (root != null) dfs(root, res);
return res;
}
public void dfs(TreeNode root, List<String> res) {
// 中序遍历,先添加结点值到路径列表
path.add(root.val);
if (root.left == null && root.right == null) {
// 左右孩子结点为空,一条路径遍历结束,拼接列表中的结点值
StringBuilder sb = new StringBuilder();
for (int i = 0; i < path.size() - 1; i++) {
sb.append(path.get(i)).append("->");
}
sb.append(path.get(path.size() - 1));
// 添加到返回值列表中
res.add(sb.toString());
return;
}
// 递归
if (root.left != null) {
dfs(root.left, res);
// 回溯,每回溯一次去掉路径列表中的最后一个结点值
path.remove(path.size() - 1);
}
if (root.right != null) {
dfs(root.right, res);
// 回溯
path.remove(path.size() - 1);
}
}
}
优化:遍历的同时拼接返回的字符串,回溯时删除
class Solution {
StringBuilder path = new StringBuilder();
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
if (root != null) dfs(root, res);
return res;
}
public void dfs(TreeNode root, List<String> res) {
path.append(root.val);
if (root.left == null && root.right == null) {
res.add(String.valueOf(path));
return;
}
path.append("->");
// 记录当前结点拼接完后的字符串下标,作为回溯删除的起点
int len = path.length();
if (root.left != null) {
dfs(root.left, res);
// 回溯
path.delete(len, path.length());
}
if (root.right != null) {
dfs(root.right, res);
// 回溯
path.delete(len, path.length());
}
}
}
也可以不使用回溯:
因为 path 只有一份,递归的同时添加结点到 path 中,所以递归到一条路径结束后,需要回溯动态维护 path,回退到上一层递归时需要将 path 也回退到上一层的 path。
所以我们可以在每次进入递归的时候克隆一份 path 出来给下边的递归使用
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();
StringBuilder path = new StringBuilder();
dfs(root, res, path);
return res;
}
public void dfs(TreeNode root, List<String> res, StringBuilder s) {
if (root == null) return;
StringBuilder path = new StringBuilder(s);
path.append(root.val);
if (root.left == null && root.right == null) {
res.add(path.toString());
return;
}
path.append("->");
dfs(root.left, res, path);
dfs(root.right, res, path);
}
}
404. 左叶子之和
Leetcode 链接
给定二叉树的根节点 root ,返回所有左叶子之和。
示例 1:
输入: root = [3,9,20,null,null,15,7]
输出: 24
解释: 在这个二叉树中,有两个左叶子,分别是 9 和 15,所以返回 24
示例 2:
输入: root = [1]
输出: 0
题解: 直接遍历二叉树,难点在于怎么判断是不是左叶子结点:是通过遍历到父亲结点的时候判断的,因为自己不知道自己的父亲结点的右孩子有没有孩子结点。
class Solution {
int sum = 0;
public int sumOfLeftLeaves(TreeNode root) {
dfs(root);
return sum;
}
public void dfs(TreeNode root) {
if (root == null) return;
if (root.left != null && root.left.left == null && root.left.right == null) {
// 左孩子不为空,左孩子的左孩子为空,左孩子的右孩子为空,那么左孩子才是叶子结点
sum += root.left.val;
}
dfs(root.left);
dfs(root.right);
}
}
带返回值的递归
class Solution {
public int sumOfLeftLeaves(TreeNode root) {
return dfs(root);
}
public int dfs(TreeNode root) {
if (root == null) return 0;
int res = 0;
if (root.left != null && root.left.left == null && root.left.right == null) {
res = root.left.val;
return dfs(root.right) + res;
}
// 返回左右子树的左叶子结点和,加上当前结点的值(如果不是左叶子结点,res = 0)
return dfs(root.left) + dfs(root.right) + res;
}
}
513. 找树左下角的值
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例 1:
输入: root = [2,1,3]
输出: 1
示例 2:
输入: [1,2,3,4,null,5,6,null,null,7]
输出: 7
题解:
方式一:层序遍历简单,返回最下边一层的首个结点值
class Solution {
public int findBottomLeftValue(TreeNode root) {
TreeNode resNode = null;
Queue<TreeNode> queue = new ArrayDeque<>();
if (root != null) queue.offer(root);
while (!queue.isEmpty()) {
int len = queue.size();
// 记录首结点的值(队头结点的值)
resNode = queue.peek();
while (len-- > 0) {
TreeNode node = queue.poll();
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return resNode.val;
}
}
方式二:递归
先序遍历二叉树,同时记录最大深度,当前结点深度大于最大深度时更新返回值。因为是先序遍历,同深度的结点保存的是第一个。
class Solution {
int res = 0;
int maxDeep = -1;
public int findBottomLeftValue(TreeNode root) {
dfs(root, 0);
return res;
}
public void dfs(TreeNode root, int deep) {
if (root == null) return;
// 深度加一
deep++;
if (deep > maxDeep) {
// 大于最大深度,更新返回值和最大深度值
res = root.val;
maxDeep = deep;
}
dfs(root.left, deep);
dfs(root.right, deep);
}
}
112. 路径总和
Leetcode 链接
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
示例 1:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
示例 2:
输入:root = [1,2,3], targetSum = 5
输出:false
解释:树中存在两条根节点到叶子节点的路径:
(1 --> 2): 和为 3
(1 --> 3): 和为 4
不存在 sum = 5 的根节点到叶子节点的路径。
示例 3:
输入:root = [], targetSum = 0
输出:false
解释:由于树是空的,所以不存在根节点到叶子节点的路径。
题解: 同 二叉树的所有路径这道题
方式一:正常递归,使用 sum 记录和
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return dfs(root, targetSum, 0);
}
public boolean dfs(TreeNode root, int targetSum, int sum) {
if (root == null) return false;
// 更新 sum
sum += root.val;
if (root.left == null && root.right == null) {
// 相等返回 true
return sum == targetSum;
}
// 只要有一个路径满足即可,是或的关系
return dfs(root.left, targetSum, sum) || dfs(root.right, targetSum, sum);
}
}
方式二:空间优化
不适用额外的空间记录和
class Solution {
int sum = 0;
public boolean hasPathSum(TreeNode root, int targetSum) {
return dfs(root, targetSum);
}
public boolean dfs(TreeNode root, int targetSum) {
if (root == null) return false;
// targetSum 减法当前结点值
targetSum -= root.val;
if (root.left == null && root.right == null) {
return targetSum == 0;
}
return dfs(root.left, targetSum) || dfs(root.right, targetSum);
}
}