LeetCode 题解 -173.二叉搜索树迭代器

Leetcode 第 173. Binary Search Tree Iterator 题,题目难度 Medium。

一. 题目要求

实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。

调用 next() 将返回二叉搜索树中的下一个最小的数。

示例:

在这里插入图片描述


BSTIterator iterator = new BSTIterator(root);
iterator.next();    // 返回 3
iterator.next();    // 返回 7
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 9
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 15
iterator.hasNext(); // 返回 true
iterator.next();    // 返回 20
iterator.hasNext(); // 返回 false
  • next() 和 hasNext() 操作的时间复杂度是 O(1),并使用 O(h) 内存,其中 h 是树的高度。

二. 解题思路 & 代码

本题考察的是对二叉搜索树的遍历,最重要的一点就是明白二叉搜索树的特点,这里引用极客时间《数据结构与算法之美》中的表述:

在树中的任意一个节点,其左子树中的每个节点的值,都要小于这个节点的值,而右子树节点的值都大于这个节点的值

题目要求我们的迭代器每次迭代返回的都是二叉树中的最小值,对于二叉搜索树可以采用中序遍历的方式将元素从小到大遍历一遍。

解法 1. 中序遍历 & List 迭代

每次 next 取值和 hasNext 判断没法直接通过 操作二叉树来实现,因此需要进行数据结构的转换,这里我们使用 ArrayList,通过中序遍历将二叉树的值以从小到大的顺序存到 List 中,然后接下来的迭代针对 List 操作就非常方便了。

实现代码如下:

class BSTIterator {

    private List<TreeNode> nodeList = new ArrayList<>();
    private int len = 0;
    private int index = 0;

    public BSTIterator(TreeNode root) {
        this.inOrder(root);
        len = nodeList.size();
    }
	// 中序遍历
    private void inOrder(TreeNode node) {
        if (node == null) {
            return;
        }

        inOrder(node.left);
        nodeList.add(node);
        inOrder(node.right);
    }


    public int next() {
        return nodeList.get(index ++).val;
    }

    public boolean hasNext() {
        return index < len;
    }
}

上面代码实际运行效率为 15ms 左右,beats 90.49 % of java

解法 2. 中序遍历 & 二叉树构建

上面的解法运行效率已经算不错了,但是有一个还可以改进的地方:

  • List 需要占用额外的存储空间,空间复杂度为 O(N),不符合题目的要求。

为了不占用额外的存储空间,就只能在二叉树本身进行操作。回到二叉搜索树的特点,如果我们二叉搜索树进行如下变动,则二叉树就变成了类似链表的结构,
此时遍历就非常方便了。

在这里插入图片描述

上述变换核心逻辑是:

  • 父节点与左子节点互换,父节点变为左子节点的左子节点
  • 父节点与右子节点互换,右子节点变为其原父节点的父节点

实现代码如下:

public class BSTIterator {
    
    TreeNode current;
    public BSTIterator(TreeNode root) {
        current = root;
        if (root != null) {
        	// 创建临时根节点,其 left 节点即为二叉搜索树中的最小左子节点
            TreeNode head = new TreeNode(0);
            TreeNode lastOne = build(head, root);
            lastOne.left = null;
            // 从 最小左子节点开始遍历
            current = head.left;
        }
    }

    private TreeNode build(TreeNode listNode, TreeNode root) {
        if (root == null) return listNode;
        listNode = build(listNode, root.left);
        listNode.left = root;
        listNode = root;
        return build(listNode, root.right);
    }
		
    public boolean hasNext() {
        return current != null;
    }
    public int next() {
        int res = current.val;
        current = current.left;
        return res;
    }
}

示例中的二叉树变换之后如下:

在这里插入图片描述
实际运行效率和第一版差不多,但是省去了额外的存储空间,另外也节省了 ArrayList 插入和长度变化时的时间,整体上性能还是有所优化的。

三. 解题后记

皓哥在《左耳听风》中提到,算法题一般分两类:

  • 基础算法题:考察常用的数据结构、解题套路比如递归、DFS 等,通过做这类题可以加深对数据结构和算法的掌握。

  • 编程题:这些题的 Edge Case 和 Corner Case 有很多。需要想清楚再干,做这些题可以非常好地训练对各种情况的考虑,以及你对程序代码组织的掌控。

这道题就是典型的基础算法题,首先要对二叉搜索树的特性有所了解,然后理解其前中后序遍历与递归的使用,最后在理清变换思路实现代码。

解题时尤其要注意的是针对迭代的性能优化,不要解出来就算了,第一版我们使用了一个额外的 List,如果要遍历的二叉树过大的话可能会导致内存溢出,因此第二版降低空间复杂度的尝试是非常有必要的。

我是 AhriJ邹同学,前后端、小程序、DevOps 都搞的炸栈工程师。博客持续更新,欢迎小伙伴关注或与我私信交流,互相学习,共同进步。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值