代码随想录之二叉树篇
T144-二叉树的前序遍历
见力扣第144题[二叉树的前序遍历]
题目描述
给你二叉树的根节点 root ,返回它节点值的 前序 遍历。
我的思路
- 使用递归遍历的方式
- 前序遍历是指遍历的顺序为:父节点 -> 左孩子-> 右孩子
List<Integer> resList = new ArrayList<>();
/**
* 二叉树的前序遍历
* @param root
* @return
*/
public List<Integer> preorderTraversal(TreeNode root) {
traverse(root);
return resList;
}
private void traverse(TreeNode root) {
if (root == null) return;
resList.add(root.val);
traverse(root.left);
traverse(root.right);
}
- 时间复杂度为:,每个节点都要被访问一次
- 空间复杂度为:,递归过程中栈的开销,平均情况下为
T102-二叉树的层序遍历
见力扣第102题[二叉树的层序遍历]
题目描述
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
我的思路
- 使用队列存储每一层的节点
- 遍历每一层的节点,将孩子节点入队
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> levelList = new ArrayList<>();
LinkedList<TreeNode> q = new LinkedList<>();
if (root != null) q.offer(root);
while (!q.isEmpty()) {
int sz = q.size();
List<Integer> temp = new ArrayList<>();
for (int i = 0; i < sz; i++) {
TreeNode cur = q.pollFirst();
temp.add(cur.val);
if (cur.left != null) q.offer(cur.left);
if (cur.right != null) q.offer(cur.right);
}
levelList.add(temp);
}
return levelList;
}
- 时间复杂度:,每个节点都要访问一次
- 空间复杂度:,额外的队列存储每一层的节点
T107-二叉树的层序遍历II
见力扣第107题[二叉树的层序遍历II]
题目描述
给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
我的思路
- 和T102的解题思路相同,将层序遍历放入一个栈中
- 循环结束之后,将栈中的临时结果集弹出到结果集中
T199-二叉树的右视图
见力扣第199题[二叉树的右视图]
题目描述
给定一个二叉树的 根节点 root,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例
输入: [1,2,3,null,5,null,4]
输出: [1,3,4]
我的思路
- 二叉树的层序遍历,将层序中最后一个元素存储到结果集合中
public List<Integer> rightSideView(TreeNode root) {
List<Integer> resList = new ArrayList<>();
Queue<TreeNode> q = new ArrayDeque<>();
if (root != null) q.offer(root);
while (!q.isEmpty()) {
int sz = q.size();
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
if (i == sz - 1) {
resList.add(cur.val);
}
if (cur.left != null) q.offer(cur.left);
if (cur.right != null) q.offer(cur.right);
}
}
return resList;
}
- 时间复杂度:,每个节点都要被访问一次
- 空间复杂度:,每个节点最多进入队列一次,队列的最大长度不超过节点数量
T637-二叉树的层平均值
见力扣第637题[二叉树的层平均值]
题目描述
给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。
我的思路
- 层序遍历,记录每层的节点值总和
- 计算出平均值,存储到结果集合中
public List<Double> averageOfLevels(TreeNode root) {
List<Double> ansList = new ArrayList<>();
Queue<TreeNode> q = new LinkedList<>();
if (root != null) q.offer(root);
while (!q.isEmpty()) {
int sz = q.size();
double sum = 0.0;
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
sum += cur.val;
if (cur.left != null) q.offer(cur.left);
if (cur.right != null) q.offer(cur.right);
}
ansList.add((double)(sum / sz));
}
return ansList;
}
T429-N叉树的层序遍历
见力扣第429题[N叉树的层序遍历]
题目描述
给定一个 N 叉树,返回其节点值的层序遍历。(即从左到右,逐层遍历)。
树的序列化输入是用层序遍历,每组子节点都由 null 值分隔(参见示例)。
我的思路
- N叉树的层序遍历和二叉树的层序遍历大差不差
- 都是将非空的孩子节点放入到队列中
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> resList = new ArrayList<>();
Queue<Node> q = new LinkedList<>();
if (root != null) q.offer(root);
while (!q.isEmpty()) {
int sz = q.size();
List<Integer> temp = new ArrayList<>();
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
temp.add(cur.val);
for (Node node : cur.children) {
if (node != null) {
q.offer(node);
}
}
}
resList.add(temp);
}
return resList;
}
- 时间复杂度:
- 空间复杂度:
T515-每个树行中寻找最大值
见力扣第515题[每个树行中寻找最大值]
题目描述
给定一棵二叉树的根节点 root ,请找出该二叉树中每一层的最大值。
我的思路
- 层序遍历
- 遍历每一层的时候,更新最大值
public List<Integer> largestValues(TreeNode root) {
List<Integer> resList = new ArrayList<>();
Queue<TreeNode> q = new LinkedList<>();
if (root != null) q.offer(root);
while (!q.isEmpty()) {
int sz = q.size();
int max = Integer.MIN_VALUE;
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
max = Math.max(max, cur.val);
if (cur.left != null) q.offer(cur.left);
if (cur.right != null) q.offer(cur.right);
}
resList.add(max);
}
return resList;
}
T116-填充每个节点的下一个右侧节点指针
见力扣第116题[填充每个节点的下一个右侧节点指针]
题目描述
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
我的思路
- 使用二叉树的层序遍历
- 每一层中,将当前节点的
next指针赋值给q.peek()
public Node connect(Node root) {
Queue<Node> q = new LinkedList<>();
if (root != null) q.offer(root);
while (!q.isEmpty()) {
int sz = q.size();
for (int i = 0; i < sz; i++) {
Node cur = q.poll();
if (i == sz - 1) {
cur.next = null;
} else {
cur.next = q.peek();
}
if (cur.left != null) q.offer(cur.left);
if (cur.right != null) q.offer(cur.right);
}
}
return root;
}
解法二
- 当前层已经是一个链表了
- 遍历当前层的链表节点的子节点,构造下一层的链表
- 移动到一下层的实际头结点处
public Node connectII(Node root) {
if (root == null) return null;
Node head = root;
// 循环遍历每一层链表
while (head != null) {
// 构建下一层的链表
Node dummy = new Node();
Node temp = dummy;
// 遍历当前链表
Node cur = head;
while (cur != null) {
if (cur.left != null) {
temp.next = cur.left; // 构建子节点链表
temp = temp.next; // 指针移动
}
if (cur.right != null) {
temp.next = cur.right;
temp = temp.next;
}
cur = cur.next;
}
// 移动到下一层的头结点处
head = dummy.next;
}
return root;
}
- 时间复杂度:,访问每一个节点
- 空间复杂度:,常数级的空间存储临时指针
T104-二叉树的最大深度
见力扣第104题[二叉树的最大深度]
题目描述
给定一个二叉树 root ,返回其最大深度。
二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。
我的思路
- 后序遍历,分别获取左右子节点的深度
- 然后返回当前子树的深度
public int maxDepth(TreeNode root) {
if (root == null) return 0;
// 获取左子树的最大深度
int maxLeft = maxDepth(root.left);
// 获取右子树的最大深度
int maxRight = maxDepth(root.right);
// 当前子树的深度为
return Math.max(maxLeft, maxRight) + 1;
}
T111-树的最小深度
见力扣第111题[树的最小深度]
题目描述
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
**说明:**叶子节点是指没有子节点的节点。
我的思路
- 使用
BFS搜索,每遍历一层孩子节点,最小深度就加1 - 如果某个节点没有子节点了,直接返回
minDepth
public int minDepth(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
int minDepth = 1;
while (!q.isEmpty()) {
int sz = q.size();
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
if (cur.left == null && cur.right == null) {
return minDepth;
}
if (cur.left != null) {
q.offer(cur.left);
}
if (cur.right != null) {
q.offer(cur.right);
}
}
minDepth++;
}
return minDepth;
}
T226-反转二叉树
见力扣第226题[反转二叉树]
题目描述
给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。
我的思路
- 使用递归遍历的方式,对树的后序位置进行操作
- 如果
root节点为空,则返回null - 获得反转之后的左孩子和右孩子
- 将
root的左指针赋给右孩子,右指针赋给左孩子
/**
* 翻转二叉树
* @param root
* @return
*/
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
return invert(root);
}
/**
* 递归的方式翻转二叉树
* @param root
* @return
*/
private TreeNode invert(TreeNode root) {
if (root == null) return null;
TreeNode left = invertTree(root.left);
TreeNode right = invertTree(root.right);
root.left = right;
root.right = left;
return root;
}
- 时间复杂度:,需要遍历每个节点
- 空间复杂度:,递归栈所用空间最差为
N
T101-对称二叉树
见力扣第101题[对称二叉树]
题目描述
给你一个二叉树的根节点 root , 检查它是否轴对称。
我的思路
- 使用递归遍历的方式
- 判断这颗树是否堆成,就是判断树的两个子节点是否是对称的
- 递归函数可以写为
symmetric(left, right)
/**
* 判断是否为对称的二叉树
* @param root
* @return
*/
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
// 判断两个子树是否符合轴对称
return symmetric(root.left, root.right);
}
/**
* 判断两个树是否轴对称
* @param left
* @param right
*/
private boolean symmetric(TreeNode left, TreeNode right) {
if (left == null && right != null || right == null && left != null) return false;
if (left == null && right == null) return true;
// 两个树都不是 null
if (left.val != right.val) return false;
return symmetric(left.right, right.left) && symmetric(left.left, right.right);
}
时间复杂度:,需要遍历树的每个节点
空间复杂度:,递归栈占用的空间最多为
T222-完全二叉树的节点个数
见力扣第222题[完全二叉树的节点个数]
题目描述
给你一棵 完全二叉树 的根节点 root ,求出该树的节点个数。
完全二叉树 的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
我的思路
- 前序遍历,每遍历一个节点,计数器加1
- 返回计数器的值
对于普通的树可以使用这种解法,但是题目中是有完全二叉树的可用信息的
思路
- 对于一个满二叉树,他的节点个数可以直接用公式计算出来,即
- 其中为树的深度
- 首先判断该节点是否是满二叉树,计算出来树的深度,直接返回结果
- 如果不是的话,就递归计算树的左右孩子节点,加上当前节点
/**
* 计算当前完全二叉树的节点个数
* @param root
* @return
*/
public int countNodes(TreeNode root) {
if (root == null) return 0;
TreeNode left = root.left;
TreeNode right = root.right;
// 判断当前二叉树是否为满二叉树
int lDepth = 1;
int rDepth = 1;
while (left != null) {
lDepth++;
left = left.left;
}
while (right != null) {
rDepth++;
right = right.right;
}
// 最左和最右子树的高度一样,是满二叉树,公式求解
if (rDepth == lDepth) {
return 1 << rDepth - 1;
}
// 不是满二叉树
return countNodes(root.left) + countNodes(root.right) + 1;
}
- 时间复杂度满足,求得为
- 空间复杂度:,递归的深度
T110-平衡二叉树
见力扣第110题[平衡二叉树]
题目描述
给定一个二叉树,判断它是否是 平衡二叉树
平衡二叉树 是指该树所有节点的左右子树的深度相差不超过 1。
输入:root = [3,9,20,null,null,15,7]
输出:true
我的思路
- 使用递归遍历的方式,求出左右子树的最大深度
- 如果两个子树的深度差距超过1,则直接返回
false - 否则返回
true
public boolean isBalancedI(TreeNode root) {
if (root == null) return true;
return height(root) >= 0;
}
/**
* 判断树的深度
* @param root
* @return
*/
private int height(TreeNode root) {
if (root == null) return 0;
int lHeight = height(root.left);
int rHeight = height(root.right);
if (lHeight == -1 || rHeight == -1 || Math.abs(rHeight - lHeight) > 1) {
return -1;
}
else {
return Math.max(lHeight, rHeight) + 1;
}
}
- 时间复杂度:,采用自底向上的递归(后序位置),每个节点的计算高度和判断平衡都只需要处理一次
- 空间复杂度:,最坏的情况下,需要递归层
T257-二叉树的所有路径
见力扣第257题[二叉树的所有路径]
题目描述
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
我的思路
- 树的深度优先遍历
- 使用回溯算法实现
List<String> paths = new ArrayList<>();
/**
* 二叉树的所有路径
* @param root
* @return
*/
public List<String> binaryTreePaths(TreeNode root) {
if (root == null) return paths;
List<Integer> path = new ArrayList<>(); // 记录路径
backtrace(root, path);
return paths;
}
/**
* 回溯的方式获得二叉树的所有路径
* @param root
* @param sb
*/
private void backtrace(TreeNode root, List<Integer> path) {
// 首先将当前节点添加到路径中
path.add(root.val);
// 如果当前节点没有孩子节点,说明到头了,是一条路径
if (root.left == null && root.right == null) {
String res = toPath(path);
// 将路径加入到结果集合中
paths.add(res);
}
if (root.left != null) {
// 遍历左孩子
backtrace(root.left, path);
}
if (root.right != null) {
// 遍历右孩子
backtrace(root.right, path);
}
// 将当前节点从路径中取消
path.remove(path.size() - 1);
}
private String toPath(List<Integer> path) {
StringBuilder sb = new StringBuilder();
for (int num : path) {
sb.append(num).append("->");
}
return sb.substring(0, sb.length() - 2); // 去掉最后一个箭头
}
- 时间复杂度:,需要遍历每一个子树
- 空间复杂度:,当二叉树为平衡二叉树的时候,高度为
T404-左叶子之和
见力扣第404题[左叶子之和]
题目描述
给定二叉树的根节点 root ,返回所有左叶子之和。
我的思路
- 树的深度优先遍历,左叶子节点的位置在
root.left != null && root.left has none child
private int counts = 0;
/**
* 所有左叶子节点的和
* @param root
* @return
*/
public int sumOfLeftLeaves(TreeNode root) {
if (root == null) return counts;
findLeftLeaves(root);
return counts;
}
/**
* 递归找到左叶子
* @param root
*/
private void findLeftLeaves(TreeNode root) {
if (root == null) return;
// 当前节点是非叶子节点
TreeNode leftNode = root.left;
TreeNode rightNode = root.right;
// 找到左叶子:左叶子没孩子
if (leftNode != null && leftNode.left == null && leftNode.right == null) {
counts += leftNode.val;
}
// 递归遍历左右孩子
findLeftLeaves(leftNode);
findLeftLeaves(rightNode);
}
- 时间复杂度:
- 空间复杂度:
T513-找到树最下角的值
见力扣第513题[找到树左下角的值]
题目描述
给定一个二叉树的 根节点 root,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
我的思路
- 树的层序遍历
- 记录最后一层,最左边的数字
思路二
- 树的深度优先搜索
- 记录叶子节点的
height, val - 如果
curHeight > height,更新 - 先遍历左叶子,再遍历右叶子
private int height;
private int val;
/**
* 递归遍历
* @param root
* @return
*/
public int findBottomLeftValueI(TreeNode root) {
if (root == null) return 0;
traverse(root, 1);
return val;
}
/**
* 递归遍历整棵树
* @param root
*/
private void traverse(TreeNode root, int curHeight) {
if (curHeight > height) {
val = root.val;
height = curHeight;
}
// 遍历左节点
if (root.left != null) { traverse(root.left, curHeight + 1); }
if (root.right != null) { traverse(root.left, curHeight + 1); }
}
T112-路径总和
见力扣第112题[路径总和]
题目描述
给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。
叶子节点 是指没有子节点的节点。
我的思路
- 使用回溯,递归遍历每个节点,更新路径和
- 如果到达叶子节点的时候路径和为
sum,则将标记available置为true
private boolean available = false;
/**
* 是否存在路径和为 sum 的路径
* @param root
* @param targetSum
* @return
*/
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
int curSum = 0;
backtrace(root, curSum, targetSum);
return available;
}
/**
* 回溯 判断路径和是否为 targetSum
* @param root
* @param curSum
*/
private void backtrace(TreeNode root, int curSum, int targetSum) {
if (available || root == null) return;
// 将当前值添加到路径中
curSum += root.val;
// 当前是叶子节点
if (root.left == null && root.right == null ) {
if (curSum == targetSum) {
this.available = true; // 标记为置为 true
}
return;
}
// 遍历左右叶子节点
if (root.left != null) {
backtrace(root.left, curSum, targetSum);
}
if (root.right != null) {
backtrace(root.right, curSum, targetSum);
}
// 将当前节点移出路径
curSum -= root.val;
}
- 时间复杂度:,最坏的情况下,需要对每个节点都要访问一次
- 空间复杂度:,其中是树的高度,取决于递归时间栈空间的开销。
T106-从中序与后序遍历序列构造二叉树
见力扣第106题[从中序与后序遍历序列构造二叉树]
题目描述
给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
示例
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
我的思路
- 中序遍历数组的构成为:左子树 | 根节点 | 右子树
- 后序遍历数组的构成为:左子树 | 右子树 | 根节点
- 在后续遍历数组中,根节点固定是在最后一位的
- 可以根据根节点的值,找到中序遍历根节点的索引位置,分出左子树和右子树的范围
Map<Integer, Integer> inMap = new HashMap<>();
/**
* 从中序和后序遍历数组中构建树
* @param inorder
* @param postorder
* @return
*/
public TreeNode buildTree(int[] inorder, int[] postorder) {
// 初始化存储中序遍历索引和值的map
for (int i = 0; i < inorder.length; i++) {
inMap.put(inorder[i], i);
}
int inStart = 0;
int inEnd = inorder.length - 1;
int postStart = 0;
int postEnd = postorder.length - 1;
return build(inorder, inStart, inEnd, postorder, postStart, postEnd);
}
/**
* 递归构建树
* @param inorder
* @param inStart
* @param inEnd
* @param postorder
* @param postStart
* @param postEnd
* @return
*/
private TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {
if (postStart > postEnd) return null;
// 取出根节点
int rootVal = postorder[postEnd];
TreeNode root = new TreeNode(rootVal);
int rootIndex = inMap.get(rootVal);
// 获取左子树的数量
int leftCounts = rootIndex - inStart;
// 获取左子树在后续遍历的范围
int lPostStart = postStart;
int lPostEnd = postStart + leftCounts - 1;
// 左子树在中序遍历的范围
int lInStart = inStart;
int lInEnd = rootIndex - 1;
// 递归创建左子树
TreeNode leftNode = build(inorder, lInStart, lInEnd, postorder, lPostStart, lPostEnd);
// 获取右子树在中序和后序的范围
int rInStart = rootIndex + 1;
int rInEnd = inEnd;
int rPostStart = lPostEnd + 1;
int rPostEnd = postEnd - 1;
// 递归创建右子树
TreeNode rightNode = build(inorder, rInStart, rInEnd, postorder, rPostStart, rPostEnd);
root.left = leftNode;
root.right = rightNode;
return root;
}
- 时间复杂度:,其中为树的节点个数
- 空间复杂度:,需要使用的空间存储哈希表。
T654-创建最大二叉树
见力扣第654题[创建最大二叉树]
题目描述
给定一个不重复的整数数组 nums 。 最大二叉树 可以用下面的算法从 nums 递归地构建:
- 创建一个根节点,其值为
nums中的最大值。 - 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums 构建的 *最大二叉树* 。
示例 1:
输入:nums = [3,2,1,6,0,5]
输出:[6,3,5,null,2,0,null,null,1]
解释:递归调用如下所示:
- [3,2,1,6,0,5] 中的最大值是 6 ,左边部分是 [3,2,1] ,右边部分是 [0,5] 。
- [3,2,1] 中的最大值是 3 ,左边部分是 [] ,右边部分是 [2,1] 。
- 空数组,无子节点。
- [2,1] 中的最大值是 2 ,左边部分是 [] ,右边部分是 [1] 。
- 空数组,无子节点。
- 只有一个元素,所以子节点是一个值为 1 的节点。
- [0,5] 中的最大值是 5 ,左边部分是 [0] ,右边部分是 [] 。
- 只有一个元素,所以子节点是一个值为 0 的节点。
- 空数组,无子节点。
我的思路
- 在数组的指定范围内,找到最大值及其坐标,构建根节点
- 根据左右子树的范围,分别递归创建左右子树
- 构建树结构,返回根节点
/**
* 创建最大二叉树
* @param nums
* @return
*/
public TreeNode constructMaximumBinaryTree(int[] nums) {
if (nums == null || nums.length <= 0) return null;
int start = 0;
int end = nums.length - 1;
return construct(nums, start, end);
}
/**
* 递归创建最大二叉树
* @param nums
* @param start
* @param end
* @return
*/
private TreeNode construct(int[] nums, int start, int end) {
if (start > end) return null;
int maxVal = Integer.MIN_VALUE;
int maxIndex = -1;
// 找到最大值及其坐标
for (int i = start; i <= end; i++) {
if (nums[i] > maxVal) {
maxVal = nums[i];
maxIndex = i;
}
}
// 创建根节点
TreeNode root = new TreeNode(maxVal);
// 递归创建左右孩子节点
TreeNode leftNode = construct(nums, start, maxIndex - 1);
TreeNode rightNode = construct(nums, maxIndex + 1, end);
// 构建树
root.left = leftNode;
root.right = rightNode;
return root;
}
- 时间复杂度:,最坏的情况下,需要递归层,并且遍历个元素寻找最大值
- 空间复杂度:,最坏的情况下,递归栈所需要的栈空间
思路二[单调栈]
TODO
T617-合并二叉树
见力扣第617题[合并二叉树]
题目描述
给你两棵二叉树: root1 和 root2 。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
我的思路
- 将两颗二叉树的平面图覆盖在一起,相交相加
- 递归构建树
/**
* 合并二叉树
* @param root1
* @param root2
* @return
*/
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if (root1 == null) return root2;
else if (root2 == null) return root1;
// 将两个节点的值相加
TreeNode root = new TreeNode(root1.val + root2.val);
TreeNode leftNode = mergeTrees(root1.left, root2.left);
TreeNode rightNode = mergeTrees(root1.right, root2.right);
// 构建树
root.left = leftNode;
root.right = rightNode;
return root;
}
- 时间复杂度:,其中和分别为两个二叉树的节点。只有两个树的节点都不空的时候,才会执行合并,因此被访问到的节点不会超过较小的二叉树的节点
- 空间复杂度:,取决于递归调用的层数,不会超过较小二叉树的最大高度
T700-二叉搜索树中的搜索
见力扣第700题[二叉搜索树中的搜索]
题目描述
给定二叉搜索树(BST)的根节点 root 和一个整数值 val。
你需要在 BST 中找到节点值等于 val 的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 null 。
我的思路
- 根据二叉搜索树的特征,递归搜索
/**
* BST 中的搜索
* @param root
* @param val
* @return
*/
public TreeNode searchBST(TreeNode root, int val) {
if (root == null || root.val == val) return root;
if (root.val > val) {
// 搜索左子树
searchBST(root.left, val);
} else {
searchBST(root.right, val);
}
}
- 时间复杂度:,二分法
- 空间复杂度:,最好的情况下,树为满二叉树,树高为
T98-验证搜索二叉树
见力扣第98题[验证搜索二叉树]
题目描述
给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
我的思路
- 左子树的每个节点都小于根节点,有最大值的限制
- 右子树的每个节点都大于根节点,有最小值的限制
public boolean isValidBST(TreeNode root) {
TreeNode max = null;
TreeNode min = null;
return valid(root, max, min);
}
/**
* 递归验证是否是 BST
* @param root
* @param max
* @param min
* @return
*/
private boolean valid(TreeNode root, TreeNode max, TreeNode min) {
if (root == null) return true;
// 如果有限定最大值,说明是左子树,左子树大于最大值,返回false
if (max != null && root.val >= max.val) return false;
// 有限定最小值,说明是右子树,右子树小于最小值,返回false
if (min != null && root.val <= min.val) return false;
// 左子树最大值为 root,右子树最小值为 root
return valid(root.left, root, min)
&& valid(root.right, max, root);
}
- 时间复杂度:,每个二叉树节点最多会被访问一次
- 空间复杂度:,递归栈最坏的情况下需要递归层
T530-二叉搜索树的最小绝对差
见力扣第530题[二叉搜索树的最小绝对差]
题目描述
给你一个二叉搜索树的根节点 root ,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
示例
输入:root = [4,2,6,1,3]
输出:1
我的思路
- BST的中序遍历是一个有序数组
- 遍历数组,两两相减更新最小差值
List<Integer> inorder = new ArrayList<>();
/**
* BST 中最小绝对差值
* @param root
* @return
*/
public int getMinimumDifference(TreeNode root) {
// 前序遍历 BST
traverse(root);
int min = Integer.MAX_VALUE;
for (int i = 1; i < inorder.size(); i++) {
min = Math.min(min, inorder.get(i) - inorder.get(i - 1));
}
return min;
}
/**
* BST 的中序遍历
* @param root
*/
private void traverse(TreeNode root) {
if (root == null) return;
traverse(root.left);
inorder.add(root.val);
traverse(root.right);
}
优化方式
- 在中序遍历的时候,更新最小的值
private int ans = Integer.MAX_VALUE;
private int pre = -1; // 遍历的前一个节点
/**
* 在前序遍历的时候,更新答案
* @param root
* @return
*/
public int getMinimumDifferenceI(TreeNode root) {
inorder(root);
return ans;
}
private void inorder(TreeNode root) {
if (root == null) return;
inorder(root.left);
if (pre != -1) {
// 遍历过节点
ans = Math.min(ans, root.val - pre);
}
pre = root.val;
inorder(root.right);
}
- 时间复杂度:,遍历二叉树中的每个节点
- 空间复杂度:,最坏情况下二叉树为一个链表,需要的栈空间为
T501-二叉搜索树中的众数
见LeetCode第501题[二叉搜索树中的众数]
题目描述
给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
示例
输入:root = [1,null,2,2]
输出:[2]
我的思路
- 众数,就是出现频率,我们可以通过前序遍历,记录每个数字出现的频率
- 然后将出现数字和出现频率以
key, val的方式存储到Map中 - 最后取出若干个众数
优化思路
- 由于BST的中序遍历是一个有序数组,重复的元素都是在一起的
- 所以我们可以使用额外的变量
maxCount | count | cur来记录中间状态maxCount:表示出现的最大频率cur:当前数字count:当前数字出现的频率
List<Integer> ans = new ArrayList<>();
// 初始化状态数据
int maxCount = 1; // 最大频次
int cur = 0; // 当前数据
int count = 0; // 当前频次
/**
* 寻找 BST 中的众数
* @param root
* @return
*/
public int[] findMode(TreeNode root) {
if (root == null) return null;
// 对 root 进行中序遍历
traverse(root);
// 将 ans List转换为静态数组
int[] res = new int[ans.size()];
for (int i = 0; i < ans.size(); i++) {
res[i] = ans.get(i);
}
return res;
}
/**
* 中序遍历
* @param root
* @param maxCount
* @param cur
* @param count
* @return
*/
private void traverse(TreeNode root) {
if (root == null) return;
traverse(root.left);
// 中序遍历的逻辑
if (root.val == cur) {
// 1 更新当前数字的频次
count++;
} else {
count = 1; // 复位
cur = root.val;
}
// 根据count 和 maxCount的大小关系来维护结果数组
if (count == maxCount) {
ans.add(cur);
} else if (count > maxCount) {
// 更新最大频次
ans.clear();
ans.add(cur);
maxCount = count;
}
traverse(root.right);
}
T236-二叉树最近的公共祖先
见LeetCode第236题[二叉树最近的公共祖先]
题目描述
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。
示例
输入:root = [3,5,1,6,2,0,8,null,null,7,4], p = 5, q = 1
输出:3
解释:节点 5 和节点 1 的最近公共祖先是节点 3 。
我的思路
- DFS深度搜索,记录两个目标节点的搜索路径,也就是他们的父节点
List<TreeNode> parents1和List<TreeNode> parents2,哪个元素多,说明他的位置更深- 将元素少的数组元素都存储到
set中,对另一个数组从队尾开始遍历,判断是否存在于set中,找到的即为两个公共祖先
思路二
- 使用分解问题的思路
- 站在当前节点,我们应该考虑什么?
if (root == p || root == q || root == null),应该直接返回return root- 递归从左子树中查找,获取结果
leftNode - 递归从右子树中查找,获取结果
rightNode - 如果
leftNode节点是空的,代表左子树中找不到节点p和q,返回rightNode - 如果两个节点都是非空的,表示两个节点分别位于两个子树,则公共祖先为
root
实现代码
public TreeNode lowestCommonAncestorI(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
// 从左边子树找到的最近的公共祖先
TreeNode leftParent = lowestCommonAncestor(root.left, p, q);
// 从右子树中找到最近的公共祖先
TreeNode rightParent = lowestCommonAncestor(root.right, p, q);
if (leftParent == null) return rightParent;
if (rightParent == null) return leftParent;
return root;
}
T669-修剪搜索二叉树
见Leetcode第669题修剪搜索二叉树
题目描述
给你二叉搜索树的根节点 root ,同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树,使得所有节点的值在[low, high]中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
所以结果应当返回修剪好的二叉搜索树的新的根节点。注意,根节点可能会根据给定的边界发生改变
示例
我的思路
- 一定要用分解问题的思路去思考
- 站在当前节点:
- 当前节点的返回值什么含义:返回的是以当前节点为根节点,符合要求的新 BST
- 如何去分解问题?
- 当前节点符合范围:
- 分别获取修剪之后的左右孩子节点
- 构建新的树,并返回
- 当前节点小于范围:
- 直接返回修剪之后的右孩子
- 当前节点大于范围:
- 直接返回修剪之后的左孩子
- 当前节点符合范围:
题解
public TreeNode trimBST(TreeNode root, int low, int high) {
if(root == null) return null;
if (root.val < low) {
return trimBST(root.right, low, high);
}
else if (root.val > high) {
return trimBST(root.left, low, high);
} else {
TreeNode left = trimBST(root.left, low, high);
TreeNode right = trimBST(root.right, low, high);
root.left = left;
root.right = right;
return root;
}
}
复杂度分析
- 时间复杂度:,最坏情况下每个节点都需要遍历一次
- 空间复杂度:,使用的递归栈所占用的空间
T108-将有序数组转换为二叉搜索树
见Leecode第108题将有序数组转换为二叉搜索树
题目描述
给你一个整数数组 nums ,其中元素已经按 升序 排列,请你将其转换为一棵
平衡二叉搜索树。
我的思路
- 以分解问题的思维解决
- 子方法的返回值是什么?是已经转换好的BST树
- 站在当前节点,我们需要做如下的事情:
- 如果当前跟节点索引超过了索引范围,直接返回
null - 以中间索引的值新建根节点
- 递归获取左右孩子节点,注意索引的变换
- 构建树,并返回
- 如果当前跟节点索引超过了索引范围,直接返回
实现代码
/**
* 将有序数组转换为平衡搜索二叉树
* @param nums
* @return
*/
public TreeNode sortedArrayToBST(int[] nums) {
if (nums == null || nums.length == 0) return null;
int start = 0;
int end = nums.length - 1;
int rootIndex = start + (end - start) / 2;
return transform(nums, start, end, rootIndex);
}
/**
* 将数组转换为二叉树
* @param nums
* @param start
* @param end
* @param rootIndex
* @return
*/
private TreeNode transform(int[] nums, int start, int end, int rootIndex) {
if (rootIndex < start || rootIndex > end) return null;
TreeNode root = new TreeNode(nums[rootIndex]);
TreeNode left = transform(nums, start, rootIndex - 1, start + (rootIndex - 1 - start) / 2);
TreeNode right = transform(nums, rootIndex + 1, end, rootIndex + 1 + (end - rootIndex - 1) / 2);
root.left = left;
root.right = right;
return root;
}
- 时间复杂度:,需要遍历每一个节点
- 空间复杂度:,平衡二叉树,递归栈的深度为
T538-BST转换为累加树
见LeetCode第538题BST转换为累加树
题目描述
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
- 节点的左子树仅包含键 小于 节点键的节点。
- 节点的右子树仅包含键 大于 节点键的节点。
- 左右子树也必须是二叉搜索树。
我的思路
- BST的中序遍历,先遍历右孩子,然后处理当前节点,最后遍历左孩子
- 有一个
curSum值来记录当前的和 - 当前节点的值,为
curSum + root.val
实现代码
private int curSum = 0;
/**
* 中序遍历:右 - 中 - 左
* @param root
* @param curSum
* @return
*/
private void traverse(TreeNode root) {
if (root == null) return ;
traverse(root.right);
curSum += root.val;
root.val = curSum;
traverse(root.left);
}
- 时间复杂度:,需要遍历每一个节点
- 空间复杂度:,最坏情况下,如果树是一个链表,则递归栈深度为