理论基础
满二叉树:一棵二叉树只有度为 0 的结点和度为 2 的结点,并且度为 0 的结点在同一层
完全二叉树:对节点从上至下、左至右开始编号,其所有编号都能与 相同高度的满二叉树中的编号对应
二叉搜索树:它存储的元素必须具备 可比较性
- 任意一个节点的值都 大于 其 左子树 所有节点的值
- 任意一个节点的值都 小于 其 右子树 所有节点的值
- 它的左右子树也是一棵 二叉搜索树
平衡二叉搜索树 (AVL 树):它是一棵空树,或它的左右两个子树的高度差的绝对值不超过 1,并且左右两个子树都是一棵平衡二叉树
二叉树的存储方式
链式存储:
class TreeNode {
int val; // 当前节点的值
TreeNode left; // 左节点
TreeNode right; // 右节点
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
顺序存储:
a
------
| |
b c
--------
| | | |
d e f g
下标 i 从 0 开始
- 左子树: 2i + 1
- 右子树: 2i + 2
-------------------
0 1 2 3 4 5 6
a, b, c, d, e, f, g
-------------------
下标 i 从 1 开始
- 左子树: 2i
- 右子树: 2i + 1
----------------------
0 1 2 3 4 5 6 7
- a, b, c, d, e, f, g
----------------------
二叉树的遍历
前中后序遍历
递归的本质:
- 每一次递归调用都会把函数的 局部变量、参数值 和 返回地址 等压入调用栈中
- 递归返回的时候,从栈顶弹出上一次递归的各项参数
- 这就是递归为什么可以返回上一层位置的原因
先序遍历:144. 二叉树的前序遍历
// 递归实现
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
dfs(root, list);
return list;
}
void dfs(TreeNode node, List<Integer> list) {
if (node == null) return;
list.add(node.val); // 根
dfs(node.left, list); // 左
dfs(node.right, list); // 右
}
}
// 迭代实现
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
if (root != null) stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.right != null) stack.push(node.right); // 右
if (node.left != null) stack.push(node.left); // 左
}
return res;
}
}
后序遍历:145. 二叉树的后序遍历
// 递归实现
class Solution {
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
dfs(root, list);
return list;
}
void dfs(TreeNode node, List<Integer> list) {
if (node == null) return;
dfs(node.left, list); // 左
dfs(node.right, list); // 右
list.add(node.val); // 根
}
}
// 迭代实现
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Stack<TreeNode> stack = new Stack<>();
stack.add(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
res.add(node.val);
if (node.left != null) stack.add(node.left); // 左
if (node.right != null) stack.add(node.right); // 右
}
Collections.reverse(res); // 反转
return res;
}
中序遍历:94. 二叉树的中序遍历
// 递归实现
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
dfs(root);
return res;
}
void dfs(TreeNode node) {
if (node == null) return;
dfs(node.left); // 左
res.add(node.val); // 根
dfs(node.right); // 右
}
}
中序遍历 的 迭代实现不太容易记,建议记后面的 迭代写法统一
// 迭代实现1
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
TreeNode cur = root;
while (cur != null || !st.isEmpty()) {
if (cur != null) {
// *
st.push(cur);
cur = cur.left;
} else {
// *
cur = st.pop();
res.add(cur.val);
cur = cur.right;
}
}
return res;
}
// 迭代实现2
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode> st = new Stack<>();
TreeNode cur = root;
while (cur != null || !st.isEmpty()) {
while (cur != null) {
// *
st.push(cur);
cur = cur.left;
}
cur = st.pop();
res.add(cur.val);
cur = cur.right;
}
return res;
}
迭代写法统一*
这种写法下,三种遍历只需要调整代码顺序即可实现:
// 中序遍历
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Stack<Object> st = new Stack<>();
st.push(root);
while (!st.empty()) {
Object o = st.pop();
if (o instanceof TreeNode) {
TreeNode node = (TreeNode) o;
if (node.right != null) st.push(node.right); // *
st.push(node.val); // *
if (node.left != null) st.push(node.left); // *
} else {
res.add((Integer) o);
}
}
return res;
}
// 先序遍历
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Stack<Object> st = new Stack<>();
st.push(root);
while (!st.empty()) {
Object o = st.pop();
if (o instanceof TreeNode) {
TreeNode node = (TreeNode) o; // *
if (node.right != null) st.push(node.right); // *
if (node.left != null) st.push(node.left); // *
st.push(node.val);
} else {
res.add((Integer) o);
}
}
return res;
}
// 后序遍历
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
TreeNode cur = root;
Stack<Object> st = new Stack<>();
st.push(cur);
while (!st.isEmpty()) {
Object o = st.pop();
if (o instanceof TreeNode) {
TreeNode node = (TreeNode) o;
st.push(node.val); // *
if (node.right != null) st.push(node.right); // *
if (node.left != null) st.push(node.left); // *
} else {
res.add((Integer)o);
}
}
return res;
}
层序遍历
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
List<Integer> tmpList = new ArrayList<>();
for (int i = q.size(); i > 0; i--) {
TreeNode node = q.poll();
tmpList.add(node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
res.add(tmpList);
}
return res;
}
层序遍历相关题目
二叉树的层序遍历 II
给你二叉树的根节点 root
,返回其节点值 自底向上的层序遍历 。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
public List<List<Integer>> levelOrderBottom(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Deque<TreeNode> q = new ArrayDeque<>();
q.add(root);
while (!q.isEmpty()) {
List<Integer> tmpList = new ArrayList<>();
for (int i = q.size(); i > 0; i--) {
TreeNode node = q.poll();
tmpList.add(node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
res.add(0, tmpList); // 往头部放
}
return res;
}
二叉树的右视图
给定一个二叉树的 根节点 root
,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
每次将层次遍历的最后一个值添加到结果中:
public List<Integer> rightSideView(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
for (int i = q.size(); i > 0; i--) {
TreeNode node = q.poll();
if (i == 1) res.add(node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
}
return res;
}
dfs 将第一次访问该深度的右边元素添加到结果中:
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
dfs(root, 0);
return res;
}
void dfs(TreeNode node, int depth) {
if (node == null) return;
// 第一次访问这个深度
if (depth == res.size()) res.add(node.val);
dfs(node.right, depth + 1);
dfs(node.left, depth + 1);
}
}
二叉树的层平均值
给定一个非空二叉树的根节点 root
, 以数组的形式返回每一层节点的平均值。与实际答案相差 10^-5
以内的答案可以被接受。
public List<Double> averageOfLevels(TreeNode root) {
List<Double> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
double sum = 0;
int n = q.size();
for (int i = n; i > 0; i--) {
TreeNode node = q.poll();
sum += node.val;
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
res.add(sum / n);
}
return res;
}
N 叉树的层序遍历
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res = new ArrayList<>();
if (root == null) return res;
Queue<Node> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
List<Integer> tmpList = new ArrayList<>();
for (int i = q.size(); i > 0; i--) {
Node node = q.poll();
tmpList.add(node.val);
for (Node n : node.children) q.add(n);
}
res.add(tmpList);
}
return res;
}
在每个树行中找最大值
给定一棵二叉树的根节点 root
,请找出该二叉树中每一层的最大值。
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
int max = Integer.MIN_VALUE;
for (int i = q.size(); i > 0; i--) {
TreeNode node = q.poll();
max = Math.max(max, node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
res.add(max);
}
return res;
}
填充每个节点的下一个右侧节点指针
层序遍历:
public Node connect(Node root) {
if (root == null) return root;
Queue<Node> q = new LinkedList<>();
q.add(root);
while (!q.isEmpty()) {
for (int i = q.size(); i > 0; i--) {
Node node = q.poll();
if (i > 1) node.next = q.peek(); // 除最后一个元素,都指向下一元素
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
}
return root;
}
DFS:
public Node connect(Node root) {
if (root == null) return root;
if (root.left != null) {
root.left.next = root.right; // 亲兄弟建立连接
if (root.next != null) // 堂兄弟建立连接
root.right.next = root.next.left;
}
connect(root.left);
connect(root.right);
return root;
}
填充每个节点的下一个右侧节点指针 II
这题用层序遍历的解法的话,和 116. 填充每个节点的下一个右侧节点指针 - 力扣(LeetCode) 一模一样。
DFS 的做法:
class Solution {
public Node connect(Node root) {
if (root == null) return root;
// 左右子树都不为 null, 直接连接
if (root.left != null && root.right != null)
root.left.next = root.right;
// 左不为 null, 右为 null, 则右子树的 next 由根的 next 得出
if (root.left != null && root.right == null)
root.left.next = getNext(root.next);
// 右不为 null
if (root.right != null)
root.right.next = getNext(root.next);
connect(root.right); // 必须先 right 再 left
connect(root.left);
return root;
}
// 找到 node 最左的第一个节点
Node getNext(Node node) {
if (node == null) return null;
// 先尝试找左节点
if (node.left != null) return node.left;
// 再尝试找右节点
if (node.right != null) return node.right;
// 左右节点都不存在,尝试对 node.next 进行相同操作
if (node.next != null) return getNext(node.next);
return null;
}
}
二叉树的最大深度
给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
递归:
public int maxDepth(TreeNode root) {
if (root == null) return 0;
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
DFS:
class Solution {
int maxLevel = 0;
public int maxDepth(TreeNode root) {
dfs(root, 1);
return maxLevel;
}
void dfs(TreeNode node, int level) {
if (node == null) return;
if (level > maxLevel) maxLevel = level;
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
}
BFS:
public int maxDepth(TreeNode root) {
if (root == null) return 0;
Deque<TreeNode> q = new ArrayDeque<>();
q.add(root);
int cnt = 0;
while (!q.isEmpty()) {
for (int i = q.size(); i > 0; i--) {
TreeNode node = q.poll();
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
cnt ++ ;
}
return cnt;
}
二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
层序遍历:最快
public int minDepth(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.add(root);
int level = 1;
while (!q.isEmpty()) {
for (int i = q.size(); i > 0; i--) {
TreeNode node = q.poll();
// 层序遍历时, 遇到第一个叶子节点,返回当前树的高度
if (node.left == null && node.right == null)
return level;
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
level++;
}
return level;
}
DFS:
class Solution {
int minLevel = Integer.MAX_VALUE;
public int minDepth(TreeNode root) {
if (root == null) return 0;
dfs(root, 1);
return minLevel;
}
void dfs(TreeNode node, int level) {
if (node == null) return;
if (node.left == null && node.right == null)
minLevel = Math.min(minLevel, level);
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
}
递归:
public int minDepth(TreeNode root) {
if (root == null) return 0;
// 处理只有一个子节点的情况
if (root.left == null && root.right != null)
return minDepth(root.right) + 1;
if (root.right == null && root.left != null)
return minDepth(root.left) + 1;
return Math.min(minDepth(root.left), minDepth(root.right)) + 1;
}
二叉树题目
翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
递归:
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
// 交换左右子节点
TreeNode tmpNode = root.left;
root.left = root.right;
root.right = tmpNode;
invertTree(root.left);
invertTree(root.right);
return root;
}
BFS:
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
// 交换子树
TreeNode t = node.right;
node.right = node.left;
node.left = t;
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
return root;
}
DFS:
public TreeNode invertTree(TreeNode root) {
if (root == null) return null;
// 交换子树
TreeNode t = invertTree(root.left);
root.left = invertTree(root.right);
root.right = t;
return root;
}
对称二叉树
给你一个二叉树的根节点 root
, 检查它是否轴对称。
递归:
class Solution {
public boolean isSymmetric(TreeNode root) {
if (root == null) return true;
return helper(root.left, root.right);
}
boolean helper(TreeNode n1, TreeNode n2) {
if (n1 == null && n2 == null) return true;
if (n1 == null || n2 == null || n1.val != n2.val) return false;
return helper(n1.left, n2.right) && helper(n1.right, n2.left);
}
}
迭代:层序遍历
public boolean isSymmetric(TreeNode root) {
Queue<TreeNode> q = new LinkedList<>();
q.offer(root.left);
q.offer(root.right);
while (!q.isEmpty()) {
TreeNode n1 = q.poll();
TreeNode n2 = q.poll();
if (n1 == null && n2 == null) continue;
if (n1 == null || n2 == null || n1.val != n2.val)
return false;
q.offer(n1.left);
q.offer(n2.right);
q.offer(n1.right);
q.offer(n2.left);
}
return true;
}
完全二叉树的节点个数*
给你一棵 完全二叉树 的根节点 root
,求出该树的节点个数。
利用完全二叉树性质的做法:
class Solution {
public int countNodes(TreeNode root) {
if (root == null) return 0;
int l = getHeight(root.left); // 左子树的高度
int r = getHeight(root.right); // 右子树的高度
if (l == r) {
// 左右子树高度相等,表示左子树一定是满二叉树
// 用公式计算左子树的节点个数:2^l - 1
// 递归计算右子树的节点个数 countNodes(root.right)
// 再 + 1 (当前节点)
return countNodes(root.right) + (1 << l); // 1 << l == 2^l
} else {
// 最后一层不满,但是倒数第二层是满的,右子树一定是满二叉树
// 公式计算右子树的节点个数: 2^r - 1
// 递归计算左子树的节点个数 countNodes(root.left)
// 再 + 1 (当前节点)
return countNodes(root.left) + (1 << r); // 1 << r == 2^r
}
}
// 完全二叉树计算高度
int getHeight(TreeNode node) {
int height = 0;
while (node != null) {
node = node.left;
height++;
}
return height;
}
}
递归:
public int countNodes(TreeNode root) {
if (root == null) return 0;
return countNodes(root.left) + countNodes(root.right) + 1;
}
DFS:
class Solution {
int num = 0;
public int countNodes(TreeNode root) {
dfs(root);
return num;
}
void dfs(TreeNode node) {
if (node == null) return;
num++;
dfs(node.left);
dfs(node.right);
}
}
层序遍历:
public int countNodes(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
int count = 0;
while (!q.isEmpty()) {
TreeNode node = q.poll();
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
count++;
}
return count;
}
平衡二叉树*
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:一个二叉树 每个节点 的左右两个子树的高度差的绝对值不超过 1 。
递归:
class Solution {
public boolean isBalanced(TreeNode root) {
if (root == null) return true;
if (Math.abs(getHeight(root.left) - getHeight(root.right)) > 1) return false;
return isBalanced(root.left) && isBalanced(root.right);
}
int getHeight(TreeNode node) {
if (node == null) return 0;
int leftH = getHeight(node.left);
int rightH = getHeight(node.right);
return Math.max(leftH, rightH) + 1;
}
}
带剪枝的递归:
class Solution {
public boolean isBalanced(TreeNode root) {
return getHeight(root) != -1;
}
int getHeight(TreeNode node) {
if (node == null) return 0;
int leftH = getHeight(node.left); // 左子树高度
int rightH = getHeight(node.right); // 右子树高度
// 剪枝:左子树 或 右子树 不满足条件 或 当前节点不满足条件,直接返回 -1
if (leftH == -1 || rightH == -1 || Math.abs(leftH - rightH) > 1) return -1;
// 左右子树都满足条件,返回当前节点的高度
return Math.max(leftH, rightH) + 1;
}
}
二叉树的所有路径
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
DFS + StringBuilder:
class Solution {
List<String> res = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
dfs(new StringBuilder(), root);
return res;
}
void dfs(StringBuilder path, TreeNode node) {
if (node == null) return;
path.append(node.val);
if (node.left == null && node.right == null) {
res.add(path.toString());
return;
}
dfs(new StringBuilder(path).append("->"), node.left);
dfs(new StringBuilder(path).append("->"), node.right);
}
}
另一棵树的子树
给你两棵二叉树 root
和 subRoot
。检验 root
中是否包含和 subRoot
具有相同结构和节点值的子树。如果存在,返回 true
;否则,返回 false
。
二叉树 tree
的一棵子树包括 tree
的某个节点和这个节点的所有后代节点。tree
也可以看做它自身的一棵子树。
class Solution {
public boolean isSubtree(TreeNode root, TreeNode subRoot) {
if (root == null) return false;
if (isSameTree(root, subRoot)) return true;
return isSubtree(root.left, subRoot) || isSubtree(root.right, subRoot);
}
// 判断 n1 和 n2 是否是相同的树
boolean isSameTree(TreeNode n1, TreeNode n2) {
if (n1 == null && n2 == null) return true;
if (n1 == null || n2 == null || n1.val != n2.val) return false;
return isSameTree(n1.left, n2.left) && isSameTree(n1.right, n2.right);
}
}
左叶子之和
给定二叉树的根节点 root
,返回所有左叶子之和。
DFS:
class Solution {
int res = 0;
public int sumOfLeftLeaves(TreeNode root) {
if (root.left == null && root.right == null) return 0;
dfs(root, null);
return res;
}
// DFS 时传入 parent 节点用来判断是否是左叶子
void dfs(TreeNode node, TreeNode parent) {
if (node == null) return;
// 判断是左叶子
if (node.left == null && node.right == null && parent.left == node)
res += node.val;
dfs(node.left, node);
dfs(node.right, node);
}
}
递归:
public int sumOfLeftLeaves(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 sumOfLeftLeaves(root.left) + sumOfLeftLeaves(root.right) + res;
}
找树左下角的值
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
层序遍历:每一层更新 res 为当前层第一个节点的值
public int findBottomLeftValue(TreeNode root) {
Queue<TreeNode> q = new LinkedList<>();
q.offer(root);
int res = root.val;
while (!q.isEmpty()) {
int size = q.size();
for (int i = size; i > 0; i--) {
TreeNode node = q.poll();
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
if (i == size) res = node.val; // 每次更新为当前层第一个节点的值
}
}
return res;
}
DFS:遍历同时求出树的深度
// DFS:遍历同时求出树的深度
class Solution {
int maxLevel = Integer.MIN_VALUE;
int res;
public int findBottomLeftValue(TreeNode root) {
dfs(root, 0);
return res;
}
void dfs(TreeNode node, int level) {
if (node == null) return;
if (node.left == null && node.right == null)
if (level > maxLevel) {
maxLevel = level;
res = node.val;
}
dfs(node.left, level + 1);
dfs(node.right, level + 1);
}
}
路径总和
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
DFS 模板:
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
return dfs(root, 0, targetSum);
}
boolean dfs(TreeNode node, int sum, int targetSum) {
if (node == null) return false;
if (node.left == null && node.right == null && sum + node.val == targetSum)
return true;
return dfs(node.left, node.val + sum, targetSum)
|| dfs(node.right, node.val + sum, targetSum);
}
}
使用 -
操作优化掉额外参数:
public boolean hasPathSum(TreeNode root, int targetSum) {
if (root == null) return false;
if (root.left == null && root.right == null && root.val == targetSum)
return true;
return hasPathSum(root.left, targetSum - root.val)
|| hasPathSum(root.right, targetSum - root.val);
}
从中序与后序遍历序列构造二叉树*
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。
参考题解:图解构造二叉树之中序+后序
递归:
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (inorder.length == 0) return null;
if (inorder.length == 1) return new TreeNode(inorder[0]);
// 初始化根节点
TreeNode root = new TreeNode(postorder[postorder.length - 1]);
// 在 inorder[] 中找到根的位置
for (int i = 0; i < inorder.length; i++) {
if (inorder[i] == root.val) {
root.left = buildTree(
Arrays.copyOfRange(inorder, 0, i),
Arrays.copyOfRange(postorder, 0, i));
root.right = buildTree(
Arrays.copyOfRange(inorder, i + 1, inorder.length),
Arrays.copyOfRange(postorder, i, postorder.length - 1));
break;
}
}
return root;
}
递归优化:传入索引进行优化 + 数组模拟哈希
class Solution {
int[] inorder_map = new int[6001]; // 数组模拟哈希
public TreeNode buildTree(int[] inorder, int[] postorder) {
if (inorder.length == 1) return new TreeNode(inorder[0]);
// 下标映射
for (int i = 0; i < inorder.length; i++)
inorder_map[inorder[i] + 3000] = i; // 映射防越界
return help(inorder, 0, inorder.length - 1,
postorder, 0, postorder.length - 1);
}
// inorder [s1, e1] postorder [s2, e2]
TreeNode help(int[] inorder, int s1, int e1, int[] postorder, int s2, int e2) {
if (s1 > e1 || s2 > e2) return null;
int rootVal = postorder[e2]; // 根节点的值
TreeNode root = new TreeNode(rootVal); // 初始化根节点
int rootValIdx = inorder_map[rootVal + 3000]; // 中序遍历中根节点值的下标
// 左子树长度 = rootValIx - s1 -1
// s2 + rootValIdx - s1 - 1 表示计算 后序数组 的结束位置,
root.left = help(inorder, s1, rootValIdx - 1,
postorder, s2, s2 + rootValIdx - s1 - 1);
root.right = help(inorder, rootValIdx + 1, e1,
postorder, s2 + rootValIdx - s1, e2 - 1);
return root;
}
}
从前序与中序遍历序列构造二叉树
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
public TreeNode buildTree(int[] preorder, int[] inorder) {
// preorder = [3,9,20,15,7] -> [3] [9] [20, 15, 7] 根左右
// inorder = [9,3,15,20,7] -> [9] [3] [15, 20, 7] 左根右
if (preorder.length == 0) return null;
TreeNode root = new TreeNode(preorder[0]);
for (int i = 0; i < inorder.length; i++) {
if (inorder[i] == root.val) {
root.left = buildTree(
Arrays.copyOfRange(preorder, 1, i + 1),
Arrays.copyOfRange(inorder, 0, i)
);
root.right = buildTree(
Arrays.copyOfRange(preorder, i + 1, preorder.length),
Arrays.copyOfRange(inorder, i + 1, inorder.length)
);
break;
}
}
return root;
}
最大二叉树
给定一个不重复的整数数组 nums
。 最大二叉树 可以用下面的算法从 nums
递归地构建:
- 创建一个根节点,其值为
nums
中的最大值。 - 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums
构建的 最大二叉树。
递归:
public TreeNode constructMaximumBinaryTree(int[] nums) {
if (nums.length == 0) return null;
int maxIdx = 0;
for (int i = 0; i < nums.length; i++)
if (nums[i] > nums[maxIdx]) maxIdx = i;
TreeNode root = new TreeNode(nums[maxIdx]);
root.left = constructMaximumBinaryTree(Arrays.copyOfRange(nums, 0, maxIdx));
root.right = constructMaximumBinaryTree(Arrays.copyOfRange(nums, maxIdx + 1, nums.length));
return root;
}
利用索引优化数组切片操作:
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
return help(nums, 0, nums.length - 1);
}
TreeNode help(int[] nums, int l, int r) {
if (l > r) return null;
int maxIdx = l;
for (int i = l; i <= r; i++)
if (nums[i] > nums[maxIdx]) maxIdx = i;
TreeNode root = new TreeNode(nums[maxIdx]);
root.left = help(nums, l, maxIdx - 1);
root.right = help(nums, maxIdx + 1, r);
return root;
}
}
合并二叉树
给你两棵二叉树: root1
和 root2
。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
注意:合并过程必须从两个树的根节点开始。
public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
if (root1 == null) return root2;
if (root2 == null) return root1;
TreeNode root = new TreeNode(root1.val + root2.val);
root.left = mergeTrees(root1.left, root2.left);
root.right = mergeTrees(root1.right, root2.right);
return root;
}
二叉搜索树题目
验证二叉搜索树
给你一个二叉树的根节点 root
,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
中序 DFS:
class Solution {
long preVal = Long.MIN_VALUE;
public boolean isValidBST(TreeNode root) {
if (root == null) return true;
// 左
if (!isValidBST(root.left)) return false;
// 中
if (root.val <= preVal) return false;
preVal = root.val; // 保存上一次遍历时的值
// 右
return isValidBST(root.right);
}
}
二叉搜索树的最小绝对差
给你一个二叉搜索树的根节点 root
,返回 树中任意两不同节点值之间的最小差值 。
差值是一个正数,其数值等于两值之差的绝对值。
中序 DFS:
class Solution {
int res = Integer.MAX_VALUE;
int prev = -100010; // 记录上一个节点的值
public int getMinimumDifference(TreeNode root) {
dfs(root);
return res;
}
void dfs(TreeNode node) {
if (node == null) return;
// 左
dfs(node.left);
// 中
res = Math.min(res, node.val - prev);
prev = node.val; // 记录上一个节点的值
// 右
dfs(node.right);
}
}
二叉搜索树中的众数
给你一个含重复值的二叉搜索树(BST)的根节点 root
,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。
如果树中有不止一个众数,可以按 任意顺序 返回。
假定 BST 满足如下定义:
- 结点左子树中所含节点的值 小于等于 当前节点的值
- 结点右子树中所含节点的值 大于等于 当前节点的值
- 左子树和右子树都是二叉搜索树
对通用的二叉树的处理:
class Solution {
final int N = 100010;
int[] map = new int[N + N]; // 某个数字的出现次数
int maxCount = 0; // 某个数字出现最多的次数
public int[] findMode(TreeNode root) {
dfs(root);
List<Integer> list = new ArrayList<>();
for (int i = 0; i < map.length; i++)
if (map[i] == maxCount) list.add(i - N);
int[] res = new int[list.size()];
int k = 0;
for (Integer i : list) res[k++] = i;
return res;
}
void dfs(TreeNode root) {
if (root == null) return;
dfs(root.left);
map[root.val + N]++; // 映射为正值
maxCount = Math.max(maxCount, map[root.val + N]);
dfs(root.right);
}
}
针对二叉搜索树进行优化:
class Solution {
List<Integer> list = new ArrayList<>();
int preVal = 0; // 上一个值
int curTimes = 0; // 当前值的出现次数
int maxTimes = 0; // 最大的出现次数
public int[] findMode(TreeNode root) {
dfs(root);
int[] res = new int[list.size()];
int k = 0;
for (Integer i : list) res[k++] = i;
return res;
}
void dfs(TreeNode node) {
if (node == null) return;
dfs(node.left);
// 判断 当前值 与 上一个值 的关系
if (preVal == node.val) curTimes++;
else {
preVal = node.val;
curTimes = 1;
}
// 判断 当前数量 和 最大数量 的关系
if (curTimes == maxTimes) list.add(node.val);
else if (curTimes > maxTimes) {
list.clear();
list.add(node.val);
maxTimes = curTimes;
}
dfs(node.right);
}
}
二叉树的最近公共祖先**
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
// root 左右两边都能找到,分别为 p, q,则 root 是最近公共祖先
if (left != null && right != null) return root;
// 只有 root 左边 能找到
if (left != null) return left;
// 只有 root 右边 能找到
if (right != null) return right;
// 左右都找不到
return null;
}
二叉搜索树的最近公共祖先
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
// 如果 p, q 值都小于 root, 说明 p, q 在 root 的左子树中
if (root.val > p.val && root.val > q.val)
return lowestCommonAncestor(root.left, p, q);
// 如果 p, q 值都大于 root, 说明 p, q 在 root 的右子树中
if (root.val < p.val && root.val < q.val)
return lowestCommonAncestor(root.right, p, q);
// p, q 分布在 root 左右,则 root 为最近公共祖先
return root;
}
二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点 root
和要插入树中的值 value
,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
递归:
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
if (root.val > val) root.left = insertIntoBST(root.left, val);
else root.right = insertIntoBST(root.right, val);
return root;
}
迭代:
public TreeNode insertIntoBST(TreeNode root, int val) {
if (root == null) return new TreeNode(val);
TreeNode node = root, parent = root; // 记录上一个节点
while (node != null) {
parent = node;
if (val > node.val) node = node.right;
else node = node.left;
}
if (val > parent.val) parent.right = new TreeNode(val);
else parent.left = new TreeNode(val);
return root;
}
删除二叉搜索树中的节点**
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
删除的三种情况:
- 度为 0 的节点(叶子节点),直接删除
- 度为 1 的节点:用子节点替代该节点
- 度为 2 的节点:用 前驱 / 后继 覆盖它,再删除 前驱 / 后继
public TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
// 目标值 < 当前节点值, 往左找
if (root.val > key) root.left = deleteNode(root.left, key);
// 目标值 > 当前节点值, 往右找
else if (root.val < key) root.right = deleteNode(root.right, key);
// 目标值 == 当前节点值, 进行删除操作
else {
// 情况 1: 度为 0 的节点 (叶子节点): 直接删除
if (root.left == null && root.right == null) return null;
// 情况 2: 度为 1 的节点: 用子节点替代该节点
else if (root.right == null) return root.left;
else if (root.left == null) return root.right;
// 情况 3: 度为 2 的节点: 用 前驱/后继 覆盖它, 再删除 前驱/后继
else {
// 前驱: 左节点的最右节点
TreeNode pre = root.left;
while (pre.right != null) pre = pre.right; // 寻找前驱
root.val = pre.val; // 用前驱覆盖当前节点
root.left = deleteNode(root.left, pre.val); // 删除前驱
// 后继: 右节点的最左节点
// TreeNode suc = root.right;
// while (suc.left != null) suc = suc.left; // 寻找后继
// root.val = suc.val; // 用后继覆盖当前节点
// root.right = deleteNode(root.right, suc.val); // 删除后继
}
}
return root;
}
修剪二叉搜索树
给你二叉搜索树的根节点 root
,同时给定最小边界low
和最大边界 high
。通过修剪二叉搜索树,使得所有节点的值在[low, high]
中。修剪树 不应该 改变保留在树中的元素的相对结构 (即,如果没有被移除,原有的父代子代关系都应当保留)。 可以证明,存在 唯一的答案 。
public TreeNode trimBST(TreeNode root, int low, int high) {
if (root == null) return null;
// root.val < low 则 root.left 下面的节点值都 < low
if (root.val < low) return trimBST(root.right, low, high);
// root.val > high 则 root.right 下面的值都 > high
if (root.val > high) return trimBST(root.left, low, high);
// 递归调用左右子树
root.left = trimBST(root.left, low, high);
root.right = trimBST(root.right, low, high);
return root;
}
将有序数组转换为二叉搜索树
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
好理解的递归:
public TreeNode sortedArrayToBST(int[] nums) {
if (nums.length == 0) return null;
int mid = nums.length >> 1;
TreeNode root = new TreeNode(nums[mid]);
root.left = sortedArrayToBST(Arrays.copyOfRange(nums, 0, mid));
root.right = sortedArrayToBST(Arrays.copyOfRange(nums, mid + 1, nums.length));
return root;
}
利用索引优化上面递归中的数组拷贝:
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
return helper(nums, 0, nums.length - 1);
}
TreeNode helper(int[] nums, int l, int r) {
if (l > r ) return null;
int mid = (l + r) >> 1;
TreeNode root = new TreeNode(nums[mid]);
root.left = helper(nums, l, mid - 1);
root.right = helper(nums, mid + 1, r);
return root;
}
}
Go 语言记录
Go 的先序递归实现:
func preorderTraversal(root *TreeNode) []int {
res := make([]int, 0)
if root == nil {
return res
}
st := list.New()
st.PushBack(root)
for st.Len() > 0 {
node := st.Remove(st.Back()).(*TreeNode)
res = append(res, node.Val)
if node.Right != nil {
st.PushBack(node.Right)
}
if node.Left != nil {
st.PushBack(node.Left)
}
}
return res
}
Go 的中序递归实现:
// go - 全局变量
var arr []int
func inorderTraversal(root *TreeNode) []int {
arr = make([]int, 0)
dfs(root)
return arr
}
func dfs(root *TreeNode) {
if root != nil {
dfs(root.Left)
arr = append(arr, root.Val)
dfs(root.Right)
}
}
// go - 闭包
func inorderTraversal(root *TreeNode) []int {
arr := make([]int, 0)
var dfs func(*TreeNode)
dfs = func(root *TreeNode) {
if root == nil {
return
}
dfs(root.Left)
arr = append(arr, root.Val)
dfs(root.Right)
}
dfs(root)
return arr
}
// go - 切片当参数传递
func inorderTraversal(root *TreeNode) []int {
arr := make([]int, 0)
dfs(root, &arr)
return arr
}
func dfs(root *TreeNode, arr *[]int) {
if root == nil {
return
}
dfs(root.Left, arr)
*arr = append(*arr, root.Val)
dfs(root.Right, arr)
}