LeetCode 热题 100 之 230. 二叉搜索树中第 K 小的元素 199. 二叉树的右视图 114. 二叉树展开为链表

  1. 二叉搜索树中第 K 小的元素

  2. 二叉树的右视图

  3. 二叉树展开为链表

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 遍历省空间、反向头插、分治返回尾节点,多种写法本质都是按先序修改指针,左置空右相连
相关推荐
一个有温度的技术博主2 小时前
Redis Cluster 核心原理:哈希槽与数据路由实战
redis·算法·缓存·哈希算法
wfbcg2 小时前
每日算法练习:LeetCode 15. 三数之和 ✅
算法·leetcode·职场和发展
2301_822703202 小时前
开源鸿蒙跨平台Flutter开发:跨端图形渲染引擎的类型边界与命名空间陷阱:以多维雷达图绘制中的 dart:ui 及 StrokeJoin 异常为例
算法·flutter·ui·开源·图形渲染·harmonyos·鸿蒙
y = xⁿ2 小时前
【LeetCode Hot100】双指针:分离指针
算法·leetcode
学习永无止境@2 小时前
Verilog中有符号数计算
图像处理·算法·fpga开发
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 41. 缺失的第一个正数 | C++ 原地哈希题解
c++·leetcode·哈希算法
小肝一下2 小时前
每日两道力扣,day6
数据结构·c++·算法·leetcode·双指针·hot100
ambition202423 小时前
【算法详解】飞机降落问题:DFS剪枝解决调度问题
c语言·数据结构·c++·算法·深度优先·图搜索算法
徒 花3 小时前
Python知识学习08
java·python·算法