二叉树
将有序数组转换为二叉搜索树
https://leetcode.cn/problems/convert-sorted-array-to-binary-search-tree/
核心思路
高度平衡的二叉搜索树定义为:一棵二叉树,其中每个节点的左右子树的高度差不超过1。
-
二叉搜索树的中序遍历结果是有序的。
-
高度平衡的二叉搜索树可以通过递归地选择数组的中点作为树的根节点来构建:
-
中点(nums[mid])作为当前子树的根节点。
-
左半部分(nums[ll…mid-1])递归构建左子树。
-
右半部分(nums[mid+1…rr])递归构建右子树。
-
示例代码
/**
* Definition for a binary tree node.
* public 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;
* }
* }
*/
class Solution {
public TreeNode sortedArrayToBST(int[] nums) {
// 主函数:将有序数组 nums 转换为高度平衡的二叉搜索树
return fenzhi(nums, 0, nums.length - 1); // 调用递归函数 fenzhi,传入数组和初始左右边界
}
TreeNode fenzhi(int[] nums, int ll, int rr) {
// 递归函数 fenzhi:将 nums[ll...rr] 转换为高度平衡的二叉搜索树
if (ll > rr) return null; // 递归出口:如果左边界大于右边界,返回 null(表示无节点)
int mid = (ll + rr) >> 1; // 计算中点位置(等价于 (ll + rr) / 2),使用位运算提高效率
TreeNode root = new TreeNode(nums[mid]); // 创建当前节点,值为数组的中点元素 nums[mid]
root.left = fenzhi(nums, ll, mid - 1); // 递归构建左子树,范围为 [ll, mid-1]
root.right = fenzhi(nums, mid + 1, rr); // 递归构建右子树,范围为 [mid+1, rr]
return root; // 返回当前子树的根节点
}
}
二叉树的层序遍历
https://leetcode.cn/problems/binary-tree-level-order-traversal/
核心思路
bfs基本操作
示例代码
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) return List.of();
List<List<Integer>> ans = new ArrayList<>();
Queue<TreeNode> q = new ArrayDeque<>();
q.add(root);
while(!q.isEmpty()){
int n = q.size();
List<Integer> vals = new ArrayList<>();
while(n-- > 0){
TreeNode node = q.poll();
vals.add(node.val);
if (node.left != null) q.add(node.left);
if (node.right != null) q.add(node.right);
}
ans.add(vals);
}
return ans;
}
}
验证二叉搜索树
https://leetcode.cn/problems/validate-binary-search-tree/
有效 二叉搜索树定义如下:
- 节点的左子树只包含 小于 当前节点的数。
- 节点的右子树只包含 大于 当前节点的数。
- 所有左子树和右子树自身必须也是二叉搜索树。
核心思路
使用递归的方法来验证每个节点是否满足二叉搜索树的性质。在递归过程中,传递一个有效值范围 [left, right],每个节点的值必须落在这个范围内。对于左子树,更新右边界为当前节点值;对于右子树,更新左边界为当前节点值。
示例代码
class Solution {
int min = Integer.MIN_VALUE;
int max = Integer.MAX_VALUE;
public boolean isValidBST(TreeNode root) {
return isValidBST(root, Long.MIN_VALUE, Long.MAX_VALUE);
}
private boolean isValidBST(TreeNode node, long left, long right) {
if (node == null) {
return true;
}
long x = node.val;
return left < x && x < right &&
isValidBST(node.left, left, x) &&
isValidBST(node.right, x, right);
}
}
二叉搜索树中第k小的元素
https://leetcode.cn/problems/kth-smallest-element-in-a-bst/
核心思路
这道题关键是理解二叉树遍历的三种方式,此题为中序遍历。
由于中序遍历就是在从小到大遍历节点值,所以遍历到的第 k 个节点值就是答案。
示例代码
class Solution {
int cnt;
int ans;
public int kthSmallest(TreeNode root, int k) {
dfs(root,k);
return ans;
}
void dfs(TreeNode root,int k){
if(root == null) return;
dfs(root.left,k);
if(++cnt == k) ans = root.val;
dfs(root.right,k);
}
}
二叉树的右视图
https://leetcode.cn/problems/binary-tree-right-side-view/
核心思路
dfs:先递归右子树,再递归左子树;同时遇到更大的(新的)深度就记录此节点的值;符合题目要求;
示例代码
class Solution {
int max = -1;
List<Integer> list = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
dfs(root,1);
return list;
}
private void dfs(TreeNode root,int deep){
if(root == null) return;
if(deep>max){
max = deep;
list.add(root.val);
}
dfs(root.right,deep+1);
dfs(root.left,deep+1);
}
}
二叉树展开为链表
https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/
核心思路
题目是先序遍历,但如果我们先序遍历的话,那就会破坏树本身的结构,所以只能从树的底部开始,先右子树,再左子树,再节点,倒着来。
示例代码
class Solution {
TreeNode head;
public void flatten(TreeNode root) {
dfs(root);
}
void dfs(TreeNode root){
if(root == null) return;
dfs(root.right);
dfs(root.left);
root.left = null;
root.right = head;
head = root;
}
}
从前序与中序遍历序列构造二叉树
https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/
核心思路
递归下面过程:
- 前序遍历的首元素 为 树的根节点 node 的值。
- 在中序遍历中找到根节点 node 的索引 ,可将 中序遍历 划分为 [ 左子树 | 根节点 | 右子树 ] 。
- 根据中序遍历中的左(右)子树的节点数量,可将 前序遍历 划分为 [ 根节点 | 左子树 | 右子树 ] 。
- 递归构建左子树和右子树:
-左子树的前序和中序范围为相应的索引区间。
-右子树的前序和中序范围根据左子树的大小进行调整。 - 返回构建的当前节点(包含其左右子树)。
dfs过程 :见注释
示例代码
class Solution {
Map<Integer,Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
for(int i = 0;i<n;i++) map.put(inorder[i],i);
return dfs(preorder,0,n,0,n);
}
TreeNode dfs(int[] preorder,int prel,int prer,int inl,int inr){
//递归的结束条件(即当前子树为空),如果是,则返回null。
if(prel == prer)return null;
//获取当前根节点的值(前序遍历的第一个元素),并在中序遍历中找到该节点的索引,以确定左子树的大小。
int leftSize = map.get(preorder[prel]) - inl;
//左子树的前序和中序范围为相应的索引区间。
TreeNode left = dfs(preorder,prel+1,prel+1+leftSize,inl,inl+leftSize);
//右子树的前序和中序范围根据左子树的大小进行调整。
TreeNode right = dfs(preorder,prel+1+leftSize,prer,inl+leftSize+1,inr);
//返回构建的当前节点(包含其左右子树)。
return new TreeNode(preorder[prel],left,right);
}
}
路径总和III
https://leetcode.cn/problems/path-sum-iii/
核心思路
1.首先理解路径:路径方向必须是向下的(只能从父节点到子节点)
2.再理解map是什么?key:不同路径和的值,value:路径和=key的个数。重点:需要注意的是,map维护的是,某个节点往上走的最长路径 所构成的 不同路径 的集合;如果不维护,会重复计算。
3.因为求的是,长路径和 - 短路径和 = targetSum
,所以 长路径和 - targetSum
= 短路径和;所以我们边计算路径和,边记录下map;
示例代码
class Solution {
// key:不同前缀和的值,value:前缀和=key的个数
Map<Long, Integer> map = new HashMap<>();
int targetSum;
int cnt;
public int pathSum(TreeNode root, int targetSum) {
if(root == null) return 0;
this.targetSum = targetSum;
// 对于下标为 0 的元素,前缀和为 0,个数为 1
map.put(0L, 1);
dfs(root,0L);
return cnt;
}
void dfs(TreeNode root,Long sum){
sum+=root.val;
Long tmp = sum-targetSum;
if(map.containsKey(tmp)) cnt+=map.get(tmp);
map.put(sum,map.getOrDefault(sum,0)+1);
if(root.left!= null) dfs(root.left,sum);
if(root.right!= null) dfs(root.right,sum);
//维护map
map.put(sum,map.get(sum)-1);
}
}
二叉树的最近公共祖先
https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
核心思路
-
如果当前节点为空(到达底部)或当前节点就是 p 或 q,直接返回当前节点。
-
如果找到了 p 或 q,则说明当前节点可能是最近公共祖先。
-
如果到达空节点,则返回空值(递归的终止条件)。
-
-
递归地在当前节点的左子树中寻找 p 和 q 的最近公共祖先。
-
递归地在当前节点的右子树中寻找 p 和 q 的最近公共祖先。
-
根据左右子树的返回结果:
-
如果左子树和右子树的结果都不为空,说明 p 和 q 分别位于当前节点的左右两侧,因此当前节点就是最近公共祖先。
-
如果只有一侧子树的结果不为空,说明 p 和 q 都在这一侧子树中,因此返回这一侧的结果。
-
如果两侧子树的结果都为空,说明 p 和 q 都不在当前子树中,返回空值。
-
示例代码
class Solution {
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;
}
}
二叉树中的最大路径和
https://leetcode.cn/problems/binary-tree-maximum-path-sum/
核心思路
- 定义问题
最大路径和:在二叉树中,路径和是指从某一节点开始,沿着父子连接的路径向下到任何节点的路径和。路径可以从任何节点开始,也可以到任何节点结束。 - 使用深度优先搜索 (DFS)
我们利用深度优先搜索来遍历树的每一个节点,并计算从当前节点出发的最大路径和。 - 递归函数 dfs(TreeNode node)
基本情况:如果当前节点 node 是 null,返回 0,因为空节点的贡献为 0。
递归计算:
计算左子树的最大路径和 ll,通过调用 dfs(root.left)。
计算右子树的最大路径和 rr,通过调用 dfs(root.right)。 - 更新最大路径和 ans
在每个节点上,计算经过该节点的路径和,即 ll + rr + root.val。
使用 Math.max 更新 ans,确保其保持最大值。 - 返回当前节点的最大贡献
返回当前节点的最大贡献是 Math.max(Math.max(ll, rr) + root.val, 0),这表示:
如果左子树或右子树的最大路径和为负数,则不考虑这部分,返回 0。
示例代码
class Solution {
int ans = Integer.MIN_VALUE;
public int maxPathSum(TreeNode root) {
if (root.left == null && root.right == null) {
return root.val;
}
dfs(root);
return ans;
}
int dfs(TreeNode root) {
if (root == null){
return 0;
}
int ll = dfs(root.left); // 左子树最大链和
int rr = dfs(root.right); // 右子树最大链和
ans = Math.max(ans, ll + rr + root.val); // 两条链拼成路径
return Math.max(Math.max(ll, rr) + root.val, 0);
}
}
以下为基础题
二叉树的中序遍历
https://leetcode.cn/problems/binary-tree-inorder-traversal/
核心思路
基础
示例代码
/**
* Definition for a binary tree node.
* public 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;
* }
* }
*/
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
List<Integer> List = new ArrayList<>();
dfs(root,List);
return List;
}
void dfs(TreeNode root,List<Integer> List){
if(root == null) return;
dfs(root.left,List);
List.add(root.val);
dfs(root.right,List);
}
}
二叉树的最大深度
https://leetcode.cn/problems/maximum-depth-of-binary-tree/
核心思路
dfs搜索
示例代码
class Solution {
int max = -1;
public int maxDepth(TreeNode root) {
if (root == null) return 0;
dfs(root,0);
return max;
}
public void dfs(TreeNode root,int deep) {
if (root == null) return;
max = Math.max(max,deep+1);
dfs(root.left,deep+1);
dfs(root.right,deep+1);
}
}
翻转二叉树
https://leetcode.cn/problems/invert-binary-tree/
核心思路
基础
示例代码
class Solution {
public TreeNode invertTree(TreeNode root) {
if (root == null) {
return null;
}
TreeNode tmp = root.left; // 交换左右儿子
root.left = root.right;
root.right = tmp;
invertTree(root.left);
invertTree(root.right);
return root;
}
}
对称二叉树
https://leetcode.cn/problems/symmetric-tree/
核心思路
递归
示例代码
class Solution {
public boolean isSymmetric(TreeNode root) {
return isMirror(root, root);
}
public boolean isMirror(TreeNode t1, TreeNode t2) {
if (t1 == null && t2 == null)
return true;
if (t1 == null || t2 == null)
return false;
if (t1.val != t2.val)
return false;
return isMirror(t1.right, t2.left)
&& isMirror(t1.left, t2.right);
}
}
二叉树的直径
https://leetcode.cn/problems/diameter-of-binary-tree/
核心思路
dfs返回当前左子树和右子树的较大值
示例代码
class Solution {
private int ans;
public int diameterOfBinaryTree(TreeNode root) {
dfs(root);
return ans;
}
private int dfs(TreeNode node) {
if (node == null) {
return -1;
}
int lLen = dfs(node.left) + 1;
int rLen = dfs(node.right) + 1;
ans = Math.max(ans, lLen + rLen);
return Math.max(lLen, rLen);
}
}