二叉树




94.二叉树的中序遍历
java
/**
* 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> res = new ArrayList<>();
inorder(root,res);
return res;
}
public void inorder(TreeNode root,List<Integer> res){
if(root == null)return;
inorder(root.left,res);
res.add(root.val);
inorder(root.right,res);
}
}
104.二叉树的最大深度
递归
java
/**
* 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 int maxDepth(TreeNode root) {
if(root == null)return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
return Math.max(left,right) + 1;
}
}
226. 翻转二叉树
先递归到底,再交换
java
/**
* 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 invertTree(TreeNode root) {
if (root == null)
return null;
invertTree(root.left);
invertTree(root.right);
TreeNode temp = root.left;
root.left = root.right;
root.right = temp;
return root;
}
}
101.对称二叉树
将整棵树的对称问题,转化为判断"左子树"和"右子树"是否互为镜像。通过 check 函数,每次递归都严格比较两个节点的值是否相等,然后让左节点的"左孩子"与右节点的"右孩子"对比,同时让左节点的"右孩子"与右节点的"左孩子"对比(即交叉比较),一路递归到底,只要所有交叉对应的节点都匹配,整棵树就是对称的
java
/**
* 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 boolean isSymmetric(TreeNode root) {
return check(root.left,root.right);
}
public boolean check(TreeNode left,TreeNode right){
//两边都为空 对称
if(left == null && right == null)return true;
//只有一边为空或者值不同 不对称
if(left == null || right == null || left.val != right.val)return false;
//继续向下交叉比较
return check(left.left,right.right) && check(left.right,right.left);
}
}
543. 二叉树的直径
遍历二叉树,在计算最大深度的同时,顺带把直径算出来
在当前节点拐点的直径长度 = 左子树的最大深度 + 右子树的最大深度
返回给父节点的是当前子树的最大深度= max(左子树的最大深度,右子树的最大深度)+1
java
/**
* 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 {
private int res = 0;
public int diameterOfBinaryTree(TreeNode root) {
maxDepth(root);
return res;
}
public int maxDepth(TreeNode root){
if(root == null)return 0;
int left = maxDepth(root.left);
int right = maxDepth(root.right);
res = Math.max(res,left + right);
return Math.max(left,right) + 1;
}
}
102.二叉树的层序遍历
BFS
- cur数组存当前正在遍历的节点
- nxt数组存被遍历节点的左右子节点
- vals数组存部分答案
- 遍历cur,把左右子节点记录到nxt中,同时把节点值记录到数组vals中,遍历结束后把vals加到答案里
- 遍历结束把cur替换成nxt,开始下一轮循环
- cur不为空就证明还没遍历完
java
/**
* 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<List<Integer>> levelOrder(TreeNode root) {
if(root == null)return List.of();
List<List<Integer>> ans = new ArrayList<>();
List<TreeNode> cur = List.of(root);
while(!cur.isEmpty()){
List<TreeNode> nxt = new ArrayList<>();
List<Integer> vals = new ArrayList<>(cur.size());
for(TreeNode node : cur){
vals.add(node.val);
if(node.left != null)nxt.add(node.left);
if (node.right != null) nxt.add(node.right);
}
cur = nxt;
ans.add(vals);
}
return ans;
}
}
优化一下,把cur数组和nxt数组用一个队列替代
java
/**
* 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<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<>(n);
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);
n--;
}
ans.add(vals);
}
return ans;
}
}
108. 将有序数组转换为二叉搜索树
平衡二叉搜索树:每个节点的左子树和右子树高度相差不超过1
由于给定的数组是严格升序的,要构建一棵高度平衡的二叉搜索树(BST),关键在于每次都选取当前区间的中间元素作为根节点 ,这样能保证左右子树的节点数量尽可能相等;随后,以中间元素为界,将数组一分为二,递归地对左半区间构建左子树、对右半区间构建右子树,直到区间越界(left > right)时返回 null 作为递归出口,最终自底向上拼接出一棵完美的平衡二叉搜索树。
java
/**
* 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) {
return build(nums,0,nums.length-1);
}
public TreeNode build(int[] nums,int left,int right){
if(left > right)return null;
int mid = (left + right)/2;
TreeNode root = new TreeNode(nums[mid]);
root.left = build(nums,left,mid-1);
root.right = build(nums,mid +1,right);
return root;
}
}
98. 验证二叉搜索树
- 前序遍历:先判断再递归
- 中序遍历:大于上一个节点
- 后序遍历:先递归再判断
递归时除了要传当前节点,还要传开区间的范围

java
/**
* 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 boolean isValidBST(TreeNode root) {
return check(root,Long.MIN_VALUE,Long.MAX_VALUE);
}
public boolean check(TreeNode root,long min,long max){
if(root == null)return true;
int temp = root.val;
if(temp <= min || temp >= max)return false;
return check(root.left,min,temp) && check(root.right,temp,max);
}
}
230. 二叉搜索树中第 K 小的元素
中序遍历 一棵二叉搜索树,输出的结果是一个从小到大排好序的数组
非递归法
用栈实现
利用栈完美模拟了二叉搜索树的中序遍历(左-根-右)过程:首先通过一个内层循环"一路向左" ,将途径的所有节点依次压入栈中直到最底端;接着从栈顶弹出节点 (此时弹出的即为当前树中的最小值),将其访问计数器加 1,若计数等于
k则直接返回该节点的值;最后将指针转向该节点的右子树 ,并在外层循环中重复上述"向左压栈、弹栈计数、转向右子树"的过程,直到精准命中第k小的元素
java
/**
* 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 int kthSmallest(TreeNode root, int k) {
Deque<TreeNode> stack = new ArrayDeque<>();
TreeNode curr = root;
int cnt = 0;
while(curr != null || !stack.isEmpty()){
//遇到节点就先压栈,一直往左走,直到走到最左下角的叶子节点
while(curr != null){
stack.push(curr);
curr = curr.left;
}
//弹栈访问得到最小的节点
curr = stack.pop();
cnt++;
if(cnt == k)return curr.val;
//转向右子树
curr = curr.right;
}
return -1;
}
}
递归法
java
/**
* 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 {
private int count = 0;
private int result = -1;
public int kthSmallest(TreeNode root, int k) {
inorder(root,k);
return result;
}
public void inorder(TreeNode node,int k){
if(node == null||result != -1)return;
inorder(node.left,k);
count++;
if(count == k){
result = node.val;
return;
}
inorder(node.right,k);
}
}
199. 二叉树的右视图

一层一层遍历,取每层最后一个数,就是右边能看到的

java
/**
* 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> rightSideView(TreeNode root) {
if (root == null) {
return List.of();
}
List<Integer> res = new ArrayList<>();
Queue<TreeNode> q = new ArrayDeque<>();
q.add(root);
while (!q.isEmpty()) {
int levelSize = q.size();
TreeNode rightNode = null;
for (int i = 0; i < levelSize; i++) {
TreeNode cur = q.poll();
if (i == levelSize - 1)
res.add(cur.val);
if (cur.left != null)
q.add(cur.left);
if (cur.right != null)
q.add(cur.right);
}
}
return res;
}
}
114. 二叉树展开为链表
java
/**
* 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 res;
public void flatten(TreeNode root) {
if(root == null)return;
//开一个列表 用来存前序遍历的节点顺序
List<TreeNode> list = new ArrayList<>();
inorder(root,list);
//遍历列表,重新把节点串成链表
for(int i=0;i<list.size()-1;i++){
TreeNode cur = list.get(i);
TreeNode next = list.get(i+1);
cur.left = null;
cur.right = next;
}
}
public void inorder(TreeNode node,List<TreeNode> list){
if(node == null)return;
list.add(node);
inorder(node.left,list);
inorder(node.right,list);
}
}
优化
首先通过递归将当前节点的左右子树分别展平为链表,接着暂存右链表,将左链表整体移到右指针上并置空左指针,最后顺着移过来的左链表一路走到最右端,将暂存的右链表拼接在末尾,从而完成当前节点的展平
java
/**
* 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 void flatten(TreeNode root) {
if(root == null)return;
flatten(root.left);//把左子树展平
flatten(root.right);//把右子树展平
TreeNode temp = root.right;
root.right = root.left;
root.left = null;
TreeNode cur = root;
while(cur.right != null)cur = cur.right;//走到原左子树的最后
cur.right = temp;//把原右子树挂到最后
}
}
105. 从前序与中序遍历序列构造二叉树
从前序遍历中找到根节点,用这个根节点取中序遍历中分开左右子树,接着继续递归分开每部分左右子树
java
/**
* 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 {
Map<Integer,Integer> inorderMap = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
for(int i=0;i<inorder.length;i++){
inorderMap.put(inorder[i],i);
}
return build(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
}
public TreeNode build(int[] preorder,int[] inorder,int preStart,int preEnd,int inStart,int inEnd){
if(preStart > preEnd || inStart > inEnd){
return null;
}
//取先序遍历初节点作为根节点
TreeNode node = new TreeNode(preorder[preStart]);
//根据根节点去中序遍历里,找到左右子树的范围
int index = inorderMap.get(node.val);
int leftPreStart = preStart + 1;
int leftPreEnd = preStart + (index - inStart);
int leftInStart = inStart;
int leftInEnd = index - 1;
node.left = build(preorder,inorder,leftPreStart,leftPreEnd,leftInStart,leftInEnd);
int rightPreStart = preStart + 1 + (index - inStart);
int rightPreEnd = preStart + (index - inStart) + (inEnd -index);
int rightInStart = index + 1;
int rightInEnd = inEnd;
node.right = build(preorder,inorder,rightPreStart,rightPreEnd,rightInStart,rightInEnd);
return node;
}
}
437. 路径总和 III
- 枚举所有路径的起点
- 计算以某个节点为起点的满足条件的路径的个数
java
/**
* 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 int pathSum(TreeNode root, int targetSum) {
if(root == null)return 0;
Queue<TreeNode> q = new ArrayDeque<>();
q.add(root);
int total = 0;
while(!q.isEmpty()){
TreeNode node = q.poll();
total += dfs(node,0,targetSum);
if(node.left != null)q.add(node.left);
if(node.right != null)q.add(node.right);
}
return total;
}
public int dfs(TreeNode node,long cur_sum,int targetSum){
if(node == null)return 0;
cur_sum += node.val;
int count = 0;
if(cur_sum == targetSum)count++;
count += dfs(node.left,cur_sum,targetSum);
count += dfs(node.right,cur_sum,targetSum);
return count;
}
}
优化
前缀和+递归+回溯
从根节点出发,每走一步就计算前缀和并"查账"看能否凑出目标值,然后"记账"并继续向下探索;探索完当前分支返回时,必须手动"擦账"以隔离不同分支,而前缀和的值则依靠递归栈自动回退
java
/**
* 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 {
int ans = 0;
public int pathSum(TreeNode root, int targetSum) {
Map<Long,Integer> map = new HashMap<>();
map.put(0L,1);
dfs(root,map,0,targetSum);
return ans;
}
public void dfs(TreeNode root,Map<Long,Integer> map,long cur,int targetSum){
if(root == null)return;
cur += root.val;
ans += map.getOrDefault(cur-targetSum,0);
map.put(cur,map.getOrDefault(cur,0)+1);
dfs(root.left,map,cur,targetSum);
dfs(root.right,map,cur,targetSum);
map.put(cur,map.getOrDefault(cur,0)-1);
return;
}
}
二分查找
36
二分查找
直接套用二分板子
c++
int bsearch_1(int l, int r)
{
while (l < r)
{
int mid = l + r >> 1; // 等价于 (l + r) / 2,但使用位运算加速除以2的操作
if (check(mid)) r = mid; // 如果中间值满足条件,缩小右边界
else l = mid + 1; // 如果中间值不满足条件,移动左边界到mid+1
}
return l; // 返回最终的左边界
}
java
class Solution {
public int searchInsert(int[] nums, int target) {
int n = nums.length;
int l = 0, r = n;//r=n:target可能不在数组中
while (l < r) {
int mid = ((r - l) >> 1) + l;
if (nums[mid] >= target) {
r = mid;
} else {
l = mid + 1;
}
}
return l;
}
}
74
二分查找
法一
通过一维数组的二分查找
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
// 连续处理,一次遍历
int low = 0, high = m * n - 1;
while (low <= high) {
int mid = low + (high - low) / 2;
// 行列转换,行整除,列取余
int num = matrix[mid / n][mid % n];
if (target == num) {
return true;
} else if (target < num) {
high = mid - 1;
} else {
low = mid + 1;
}
}
return false;
}
}
法二
在逻辑上将二维矩阵视为一个一维的有序数组,并通过数学计算(mid / n 和 mid % n)动态访问对应元素
把 m x n 的矩阵看作长度为 m\*n 的一维数组
java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
// 连续处理,一次遍历
int l = 0, r = m * n;
while (l < r) {
int mid = l + r >> 1;
// 行列转换,行整除,列取余
int num = matrix[mid / n][mid % n];
if (target == num) {
return true;
} else if (num > target) {
r = mid;
} else {
l = mid + 1;
}
}
return false;
}
}
34
法一
二分+线性扫描
- 用二分查找快速定位第一个 ≥ target 的位置(即左边界)
- 从该位置向右线性扫描,找到最后一个等于 target 的位置(右边界)
总体时间复杂度
总时间 = 二分查找 + 线性扫描 = O(log n + k)
java
class Solution {
public int[] searchRange(int[] nums, int target) {
int n = nums.length;
int begin,end;
begin = BSearch(nums,target);
end = begin;
if(begin == n || nums[begin] != target)return new int[] {-1,-1};
for(int i = begin;i < n - 1;i++){
if(nums[i + 1] != target)break;
end = i + 1;
}
return new int[] {begin,end};
}
public int BSearch(int[] nums,int target){
int n = nums.length;
int l = 0,r = n;
while(l < r){
int mid = (l + r) >> 1;
if(nums[mid] >= target)r = mid;
else l = mid + 1;
}
return r;
}
}
法二
纯二分查找
找end = 找<=target的最后一个数 = 找>target的第一个数 = 找>=target+1的第一个数
由此就可以复用二分函数
end = BSearch(nums,target + 1) - 1
java
class Solution {
public int[] searchRange(int[] nums, int target) {
int n = nums.length;
int begin,end;
begin = BSearch(nums,target);
if(begin == n || nums[begin] != target)return new int[] {-1,-1};
end = BSearch(nums,target + 1) - 1;
return new int[] {begin,end};
}
public int BSearch(int[] nums,int target){
int n = nums.length;
int l = 0,r = n;
while(l < r){
int mid = (l + r) >> 1;
if(nums[mid] >= target)r = mid;
else l = mid + 1;
}
return r;
}
}
33
初始思路:一开始想先重新排序数组,二分查找得到下标,再通过计算得出原先的下标值。

没有考虑到数组小于三的情况,而且不能改变nums
正确思路:旋转后的数组,前面的数组是有序的,后面的数组也是有序的
在常规二分查找的时候查看当前 mid 为分割位置分割出来的两个部分 l, mid 和 mid + 1, r 哪个部分是有序的
根据有序的那个部分确定我们该如何改变二分查找的上下界(因为可以根据有序的那部分判断出 target 在不在这个部分):
- 如果有序数组在mid左边
- 若target在nums\[l,numsmid],缩小范围至这个区间
- 否则缩小范围至另外一个区间
- 如果有序数组在mid右边
- 若target在nums\[mid+1,numsn-1],缩小范围至这个区间
- 否则缩小范围至另外一个区间

java
class Solution {
public int search(int[] nums, int target) {
return Search(nums,target);
}
public int Search(int[] nums,int target){
int n = nums.length;
int l = 0,r = n;
while(l < r){
int mid = (l + r) >> 1;
if(nums[mid] == target)return mid;//数组元素互不相同,找到target直接return
if(nums[mid] >= nums[0]){
if(target >= nums[0] && target <= nums[mid]) r = mid;
else l = mid + 1;
}else {
if(target >= nums[mid] && target <= nums[n-1]) l = mid + 1;
else r = mid;
}
}
return -1;
}
}