二叉搜索树中第 K 小的元素
二叉树的右视图
二叉树展开为链表
230. 二叉搜索树中第 K 小的元素

/**
* 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 res = 0;
public int kthSmallest(TreeNode root, int k) {
inorder(root, k);
return res;
}
private void inorder(TreeNode node, int k) {
if (node == null) return;
inorder(node.left, k);
// 访问当前节点,计数+1
count++;
if (count == k) {
res = node.val;
return;
}
inorder(node.right, k);
}
}
解法思路1:递归中序遍历
思路:递归执行中序遍历,用计数器记录访问顺序,当计数等于 K 时返回对应值。
class Solution {
public int kthSmallest(TreeNode root, int k) {
Stack<TreeNode> stack = new Stack<>();
TreeNode cur = root;
while (!stack.isEmpty() || cur != null) {
// 遍历到最左节点
while (cur != null) {
stack.push(cur);
cur = cur.left;
}
// 弹出栈顶(当前最小元素)
cur = stack.pop();
k--;
if (k == 0) return cur.val; // 找到第K小元素,直接返回
// 处理右子树
cur = cur.right;
}
return -1; // 题目保证k合法,不会走到这里
}
}
解法思路2:迭代中序遍历(推荐,可提前终止)
思路:用栈模拟中序遍历,访问到第 K 个元素时直接返回,无需遍历整棵树,效率更高。
灵神写法一:记录答案
在中序遍历,即「左-根-右」的过程中,每次递归完左子树,就把 k 减少 1,表示我们按照中序遍历访问到了一个节点。如果减一后 k 变成 0,那么答案就是当前节点的值,用一个外部变量 ans 记录。
class Solution {
private int ans;
private int k;
public int kthSmallest(TreeNode root, int k) {
this.k = k;
dfs(root);
return ans;
}
private void dfs(TreeNode node) {
if (node == null || k <= 0) {
return;
}
dfs(node.left); // 左
if (--k == 0) {
ans = node.val; // 根
}
dfs(node.right); // 右
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/kth-smallest-element-in-a-bst/solutions/2952810/zhong-xu-bian-li-pythonjavaccgojsrust-by-wc02/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
灵神写法二:不记录答案 + 提前返回
写法一使用了一个外部变量记录答案,能否不使用外部变量记录呢?
可以,做法如下:
递归边界:如果当前节点是空节点,返回 −1,表示没有找到。注意题目保证节点值非负。
执行中序遍历,先递归左子树。
判断左子树的返回值 leftRes 是否为 −1。如果不是 −1,说明我们在左子树中找到了答案,返回 leftRes。如果是 −1,说明尚未找到答案,继续下一步。
把 k 减少 1。如果 k=0,那么答案就是当前节点值,返回当前节点值。
现在,答案要么在当前节点的右子树中,要么在除了当前子树的其余节点中。递归右子树,如果答案在右子树中,那么直接返回答案;如果答案不在右子树中,那么右子树也会返回 −1,由于当前子树搜索完毕,所以当前子树没有找到答案,返回 −1。综上所述,可以直接返回右子树的返回值。
class Solution {
private int k;
public int kthSmallest(TreeNode root, int k) {
this.k = k;
return dfs(root);
}
private int dfs(TreeNode node) {
if (node == null) {
return -1; // 题目保证节点值非负,用 -1 表示没有找到
}
int leftRes = dfs(node.left);
if (leftRes != -1) { // 答案在左子树中
return leftRes;
}
if (--k == 0) { // 答案就是当前节点
return node.val;
}
return dfs(node.right); // 右子树会返回答案或者 -1
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/kth-smallest-element-in-a-bst/solutions/2952810/zhong-xu-bian-li-pythonjavaccgojsrust-by-wc02/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
199. 二叉树的右视图

/**
* 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) {
List<Integer> res = new ArrayList<>();
if (root == null) return res;
Queue<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
int levelSize = queue.size();
// 遍历当前层所有节点,只取最后一个
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();
// 是当前层最后一个节点,加入结果
if (i == levelSize - 1) {
res.add(node.val);
}
// 子节点入队
if (node.left != null) queue.offer(node.left);
if (node.right != null) queue.offer(node.right);
}
}
return res;
}
}
class Solution {
public List<Integer> rightSideView(TreeNode root) {
if (root == null) {
return List.of();
}
List<Integer> ans = new ArrayList<>();
List<TreeNode> cur = List.of(root);
while (!cur.isEmpty()) {
ans.add(cur.getLast().val);
List<TreeNode> nxt = new ArrayList<>();
for (TreeNode node : cur) {
if (node.left != null) nxt.add(node.left);
if (node.right != null) nxt.add(node.right);
}
cur = nxt;
}
return ans;
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/binary-tree-right-side-view/solutions/2015061/ru-he-ling-huo-yun-yong-di-gui-lai-kan-s-r1nc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解题思路1:BFS
思路:按层遍历二叉树,每一层只取最后一个节点(最右侧节点),即为右视图节点。
class Solution {
private List<Integer> res = new ArrayList<>();
public List<Integer> rightSideView(TreeNode root) {
dfs(root, 0);
return res;
}
private 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);
}
}
class Solution {
public List<Integer> rightSideView(TreeNode root) {
if (root == null) {
return List.of();
}
List<Integer> ans = new ArrayList<>();
List<TreeNode> cur = List.of(root);
while (!cur.isEmpty()) {
ans.add(cur.getLast().val);
List<TreeNode> nxt = new ArrayList<>();
for (TreeNode node : cur) {
if (node.left != null) nxt.add(node.left);
if (node.right != null) nxt.add(node.right);
}
cur = nxt;
}
return ans;
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/binary-tree-right-side-view/solutions/2015061/ru-he-ling-huo-yun-yong-di-gui-lai-kan-s-r1nc/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解题思路2:DFS
思路:按照「根 → 右 → 左」的顺序遍历,保证每一层第一个被访问的节点就是最右侧节点,用深度记录层级,首次访问该层级时加入结果。
114. 二叉树展开为链表

/**
* 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) {
List<TreeNode> list = new ArrayList<>();
preorder(root, list);
// 依次修改指针
for (int i = 1; i < list.size(); i++) {
TreeNode prev = list.get(i - 1);
TreeNode curr = list.get(i);
prev.left = null;
prev.right = curr;
}
}
// 先序遍历收集节点
private void preorder(TreeNode root, List<TreeNode> list) {
if (root == null) return;
list.add(root);
preorder(root.left, list);
preorder(root.right, list);
}
}
解法思路1:递归法(先序遍历 + 尾插)
思路:按先序遍历顺序收集所有节点,再依次修改指针,将前一节点的 right 指向后一节点,left 置空。
class Solution {
public void flatten(TreeNode root) {
if (root == null) return;
// 递归展开左右子树
flatten(root.left);
flatten(root.right);
// 保存原右子树
TreeNode right = root.right;
// 将左子树挂到根的右指针
root.right = root.left;
root.left = null;
// 找到当前右子树的末尾,将原右子树挂到末尾
TreeNode p = root;
while (p.right != null) {
p = p.right;
}
p.right = right;
}
}
解法思路2:递归法(先序遍历 + 尾插)
思路:利用先序遍历的结构,将左子树插入到根节点和右子树之间,递归处理左右子树,无需额外空间。
class Solution {
public void flatten(TreeNode root) {
TreeNode curr = root;
while (curr != null) {
if (curr.left != null) {
// 找到左子树的最右节点(先序遍历中 curr 的前驱)
TreeNode pre = curr.left;
while (pre.right != null) {
pre = pre.right;
}
// 将右子树挂到前驱的右指针
pre.right = curr.right;
// 将左子树挂到 curr 的右指针,左指针置空
curr.right = curr.left;
curr.left = null;
}
// 移动到下一个节点
curr = curr.right;
}
}
}
解法思路3:迭代法(Morris 先序遍历,O (1) 空间)
思路:用 Morris 遍历实现先序,遍历过程中直接修改指针,原地展开,无需递归栈和额外空间。
class Solution {
private TreeNode head;
public void flatten(TreeNode root) {
if (root == null) {
return;
}
flatten(root.right);
flatten(root.left);
root.left = null;
root.right = head; // 头插法,相当于链表的 root.next = head
head = root; // 现在链表头节点是 root
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/solutions/2992172/liang-chong-fang-fa-tou-cha-fa-fen-zhi-p-h9bg/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法思路4:头插法
采用头插法构建链表,也就是从节点 6 开始,在 6 的前面插入 5,在 5 的前面插入 4,依此类推。
为此,要按照 6→5→4→3→2→1 的顺序访问节点。如何遍历这棵树,才能实现这个顺序?
按照右子树 - 左子树 - 根的顺序 DFS 这棵树。
DFS 的同时,记录当前链表的头节点为 head。一开始 head 是空节点。
具体来说:
如果当前节点为空,返回。
递归右子树。
递归左子树。
把 root.left 置为空。
头插法,把 root 插在 head 的前面,也就是 root.right=head。
现在 root 是链表的头节点,把 head 更新为 root。
class Solution {
public void flatten(TreeNode root) {
dfs(root);
}
private TreeNode dfs(TreeNode root) {
if (root == null) {
return null;
}
TreeNode leftTail = dfs(root.left);
TreeNode rightTail = dfs(root.right);
if (leftTail != null) {
leftTail.right = root.right; // 左子树链表 -> 右子树链表
root.right = root.left; // 当前节点 -> 左右子树合并后的链表
root.left = null;
}
return rightTail != null ? rightTail : leftTail != null ? leftTail : root;
}
}
作者:灵茶山艾府
链接:https://leetcode.cn/problems/flatten-binary-tree-to-linked-list/solutions/2992172/liang-chong-fang-fa-tou-cha-fa-fen-zhi-p-h9bg/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
解法思路5:分治
考虑分治,假如我们计算出了 root=1 左子树的链表 2→3→4,以及右子树的链表 5→6,那么接下来只需要穿针引线,把节点 1 和两条链表连起来:
先把 2→3→4 和 5→6 连起来,也就是左子树链表尾节点 4 的 right 更新为节点 5(即 root.right),得到 2→3→4→5→6。
然后把 1 和 2→3→4→5→6 连起来,也就是节点 1 的 right 更新为节点 2(即 root.left),得到 1→2→3→4→5→6。
最后把 root.left 置为空。
上面的过程,我们需要知道左子树链表的尾节点 4。所以 DFS 需要返回链表的尾节点。
链表合并完成后,返回合并后的链表的尾节点,也就是右子树链表的尾节点。如果右子树是空的,则返回左子树链表的尾节点。如果左右子树都是空的,返回当前节点。
总结
这几道二叉树高频题,核心思路清晰统一,背熟模板即可举一反三:
- 二叉搜索树第 K 小 :利用 BST 性质,中序遍历是升序,递归 / 迭代找到第 K 个节点即为答案;
- 二叉树右视图:BFS 取每层最后一个节点,或 DFS 优先右遍历,按深度记录首个节点;
- 二叉树展开为链表 :先序遍历重构、递归拼接左右子树、Morris 遍历省空间、反向头插、分治返回尾节点,多种写法本质都是按先序修改指针,左置空右相连。
