33 二叉树的中序遍历
中序遍历是先左子树,再根节点,最后是右节点的顺序。递归的写法比较直观:
List<Integer> ans = new ArrayList<>();
public List<Integer> inorderTraversal(TreeNode root) {
dfs(root);
return ans;
}
public void dfs(TreeNode root){
if (root == null){
return;
}
dfs(root.left); // 先遍历左子树
ans.add(root.val); // 保存根节点的值
dfs(root.right); // 再遍历右子树
}
迭代的版本就是使用一个栈,模拟递归的过程:
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> ans = new ArrayList<>();
Deque<TreeNode> st = new ArrayDeque<>();
while (root != null || !st.isEmpty()){
// 先一直遍历左子树
while (root != null){
st.push(root);
root = root.left;
}
// 左子树遍历结束,保存根节点的值
root = st.pop();
ans.add(root.val);
// 切换到右子树继续遍历
root = root.right;
}
return ans;
}
34 二叉树的最大深度
根节点的深度为 0,其他节点的深度是左右子树的最大深度 + 1。
public int maxDepth(TreeNode root) {
if (root == null){
return 0;
}
int l = maxDepth(root.left);
int r = maxDepth(root.right);
return Math.max(l, r) + 1;
}
35 翻转二叉树
public TreeNode invertTree(TreeNode root) {
// 空节点不用翻转
if (root == null){
return root;
}
// 翻转左子树
TreeNode left = invertTree(root.left);
// 翻转右子树
TreeNode right = invertTree(root.right);
// 翻转当前节点
root.left = right;
root.right = left;
return root;
}
36 对称二叉树
除去根节点的两颗左右子树是对称二叉树的关键信息在于:
两颗子树的根节点相同;
一棵树的左子树和另一颗数树右子树相同;
一棵树的右子树和另一颗数树左子树相同。
public boolean isSameTree(TreeNode p, TreeNode q) {
if (p == null || q == null){
return p == q;
}
return p.val == q.val && isSameTree(p.left, q.left) && isSameTree(p.right, q.right);
}
37 二叉树的直径
int ans = 0;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return ans;
}
public int dfs(TreeNode root){
// 叶子节点链长为 0,空节点链长为 -1
if (root == null){
return -1;
}
int l = dfs(root.left) + 1; // 左子树最大链长
int r = dfs(root.right) + 1; // 右子树最大链长
ans = Math.max(ans, l + r); // 两链拼成路径
return Math.max(l, r); // 当前子树最大链长
}
38 二叉树的层序遍历
使用队列(Queue)按层遍历二叉树。
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ans = new ArrayList<>();
if (root == null){
return ans;
}
// 创建队列,插入根节点
Deque<TreeNode> q = new ArrayDeque<>();
q.offer(root);
while(!q.isEmpty()){
// 按层遍历
int n = q.size();
List<Integer> layer = new ArrayList<>();
for (int i = 0; i < n; i++){
TreeNode node = q.poll();
layer.add(node.val);
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
ans.add(layer);
}
return ans;
}
39 将有序数组转换为二叉搜索树
public TreeNode sortedArrayToBST(int[] nums) {
return dfs(nums, 0, nums.length);
}
// 把 [left, right - 1] 转换成二叉搜索树
private TreeNode dfs(int[] nums, int left, int right){
if (left == right){
return null;
}
int m = left + (right - left) / 2;
return new TreeNode(nums[m], dfs(nums, left, m), dfs(nums, m + 1, right));
}
40 验证二叉搜索树 ⭐
通过数值的区间范围来验证二叉搜索树。
public boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
public boolean isValidBST(TreeNode root, long left, long right) {
if (root == null) {
return true;
}
int x = root.val;
return left < x && x < right &&
isValidBST(root.left, left, x) &&
isValidBST(root.right, x, right);
}
41 二叉搜索树中第 K 小的元素 ⭐
二叉搜索树的中序遍历就是从小到大遍历的,中序遍历过程中第 k 个出现的元素就是答案。
int k;
int ans;
public int kthSmallest(TreeNode root, int k) {
this.k = k;
dfs(root);
return ans;
}
public void dfs(TreeNode root){
if (root == null || k == 0){
return;
}
dfs(root.left);
if (--k == 0){
ans = root.val;
}
dfs(root.right);
}
42 二叉树的右视图
使用层序遍历,将每一层的最后一个元素加入答案即可。
public List<Integer> rightSideView(TreeNode root) {
List<Integer> ans = new ArrayList<>();
if (root == null){
return ans;
}
Deque<TreeNode> q = new ArrayDeque<>();
q.offer(root);
while (!q.isEmpty()){
int n = q.size();
for (int i = 0; i < n; i ++){
TreeNode node = q.poll();
// 最右侧元素,加入答案
if (i == n - 1){
ans.add(node.val);
}
if (node.left != null) q.offer(node.left);
if (node.right != null) q.offer(node.right);
}
}
return ans;
}
本题也可以使用 DFS 解决,具体思路:先递归右子树,再递归左子树,当某个深度首次到达时,对应的节点就在右视图中。
public List<Integer> rightSideView(TreeNode root) {
List<Integer> ans = new ArrayList<>();
dfs(root, ans, 0);
return ans;
}
public void dfs(TreeNode root, List<Integer> ans, int depth) {
if (root == null){
return;
}
// 首次到达某个深度
if (ans.size() == depth){
ans.add(root.val);
}
dfs(root.right, ans, depth + 1);
dfs(root.left, ans, depth + 1);
}
43 二叉树展开为链表
头插法,从最后一个节点开始,依次向前插入节点。因此需要「右子树-左子树-根节点」的顺序 DFS 遍历树。
TreeNode head;
public void flatten(TreeNode root) {
if (root == null){
return;
}
flatten(root.right);
flatten(root.left);
root.left = null;
root.right = head;
head = root;
}
44 从前序与中序遍历序列构造二叉树 ⭐
public TreeNode buildTree(int[] preorder, int[] inorder) {
// 中,左,右
// 左,中,右
int n = preorder.length;
if (n == 0){
return null;
}
// 左子树的大小
int leftSize = indexOf(inorder, preorder[0]);
int[] preL = Arrays.copyOfRange(preorder, 1, 1 + leftSize);
int[] preR = Arrays.copyOfRange(preorder, 1 + leftSize, n);
int[] inL = Arrays.copyOfRange(inorder, 0, leftSize);
int[] inR = Arrays.copyOfRange(inorder, leftSize + 1, n);
TreeNode left = buildTree(preL, inL);
TreeNode right = buildTree(preR, inR);
return new TreeNode(preorder[0], left, right);
}
public int indexOf(int[] a, int x){
for (int i = 0; ; i++){
if (a[i] == x){
return i;
}
}
}
45 路径总和 III
int ans;
int targetSum;
Map<Long, Integer> cnt = new HashMap<>();
public int pathSum(TreeNode root, int targetSum) {
cnt.put(0L, 1);
this.targetSum = targetSum;
return dfs(root, 0);
}
public int dfs(TreeNode root, long sum) {
if (root == null) {
return 0;
}
sum += root.val;
int res = cnt.getOrDefault(sum - targetSum, 0);
cnt.merge(sum, 1, Integer::sum);
res += dfs(root.left, sum);
res += dfs(root.right, sum);
cnt.merge(sum, -1, Integer::sum); // 恢复现场
return res;
}
46 二叉树的最近公共祖先
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);
if (left != null && right != null){
return root;
}
return left != null ? left : right;
}
47 二叉树中的最大路径和
int ans = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
dfs(root);
return ans;
}
public int dfs(TreeNode root){
if (root == null){
return 0;
}
int leftVal = dfs(root.left);
int rightVal = dfs(root.right);
ans = Math.max(ans, leftVal + rightVal + root.val);
return Math.max(Math.max(leftVal, rightVal) + root.val, 0);
}
参考
部分思路参考了力扣官方题解和灵茶山艾府题解。