【LeetCode 二叉树专项】二叉搜索树中的中序后继(285)

news/2024/11/7 21:08:17/

文章目录

    • 1. 题目
      • 1.1 示例
      • 1.2 说明
      • 1.3 限制
    • 2. 解法一(递归中序遍历)
      • 2.1 分析
      • 2.2 实现
      • 2.3 复杂度
    • 3. 解法二(迭代中序遍历)
      • 3.1 分析
      • 3.2 实现
      • 3.3 复杂度

1. 题目

给定一棵二叉搜索树和其中的一个节点 p ,找到该节点在树中的中序后继。如果节点没有中序后继,请返回 null

节点 p 的后继是值比 p.val 大的节点中键值最小的节点。

1.1 示例

  • 示例 1 1 1
  • 输入: root = [2,1,3]p = 1
  • 输出: 2 2 2
  • 解释: 这里 1 1 1 的中序后继是 2 2 2 。请注意 p 和返回值都应是 TreeNode 类型。

在这里插入图片描述

  • 示例 2 2 2
  • 输入: root = [5, 3, 6, 2, 4, null, null, 1]p = 6
  • 输出: null
  • 解释: 因为给出的节点没有中序后继,所以答案就返回 null 了。

在这里插入图片描述

1.2 说明

  • 来源: 力扣(LeetCode)
  • 链接: https://leetcode-cn.com/problems/inorder-successor-in-bst

1.3 限制

  • 树中节点的数目在范围 [ 1 , 1 0 4 ] [1,\text{ }10^4] [1, 104] 内;
  • − 1 0 5 < = Node.val < = 1 0 5 -10^5 <= \text{Node.val} <= 10^5 105<=Node.val<=105
  • 树中各节点的值均保证唯一。

2. 解法一(递归中序遍历)

2.1 分析

既然要求寻找给定节点的中序遍历后继节点,则自然地可以想到先获得该二叉搜索树的中序遍历序列,然后找并返回给定节点在中序遍历序列中后一个节点即可。

因此,下面的实现先通过一次中序遍历得到二叉搜索树的一个中序遍历序列 self._nodes ,然后在其中找到节点 p 对应的索引,最后根据索引确定是否有后继节点。

2.2 实现

from typing import Optionalclass TreeNode:def __init__(self, val: int, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def __init__(self):self._nodes = []def _inorder(self, root: TreeNode):if root is None:returnself._inorder(root.left)self._nodes.append(root)self._inorder(root.right)def initialize(self):self._nodes = []def successor(self, root: TreeNode, p: TreeNode) -> Optional[TreeNode]:self._inorder(root)index = self._nodes.index(p)if index >= len(self._nodes) - 1:returnelse:return self._nodes[index + 1]def main():node6 = TreeNode(1)node5 = TreeNode(4)node4 = TreeNode(2, left=node6)node3 = TreeNode(6)node2 = TreeNode(3, left=node4, right=node5)node1 = TreeNode(5, left=node2, right=node3)root = node1sln = Solution()def print_successor(suc: TreeNode):if suc:print(suc.val)else:print(None)print_successor(sln.successor(root, node6))  # 2sln.initialize()print_successor(sln.successor(root, node2))  # 4sln.initialize()print_successor(sln.successor(root, node5))  # 5sln.initialize()print_successor(sln.successor(root, node3))  # Noneif __name__ == '__main__':main()

细心的读者可能已经发现了,在上述实现的测试代码中,每调用一次寻找后继节点的 successor() 方法之后,都调用了一次 initialize() 方法将对象的实例属性 _nodes 清空,原因在于每次调用 successor() 时,该方法都会调用一次 _inorder() 方法,如果不这么做会导致 _nodes 实例属性包含多组中序遍历序列,从而产生意料之外的错误。

实际上,稍显优雅的做法如下,即将调用 _inorder() 方法获得给定二叉搜索树中序序列的操作放在初始化方法 __init__() 中,而在 successor() 方法中仅保留获取后继节点的逻辑,这样就不会导致 _nodes 实例属性在 ElegantSolution 对象的生命周期内被追加多组中序遍历序列了。

from typing import Optionalclass TreeNode:def __init__(self, val: int, left=None, right=None):self.val = valself.left = leftself.right = rightclass ElegantSolution:def __init__(self, root: TreeNode):self._nodes = []self._inorder(root)def _inorder(self, root: TreeNode):if root is None:returnself._inorder(root.left)self._nodes.append(root)self._inorder(root.right)def successor(self, p: TreeNode) -> Optional[TreeNode]:index = self._nodes.index(p)if index >= len(self._nodes) - 1:returnelse:return self._nodes[index + 1]def print_successor(suc: TreeNode):if suc:print(suc.val)else:print(None)def main():node6 = TreeNode(1)node5 = TreeNode(4)node4 = TreeNode(2, left=node6)node3 = TreeNode(6)node2 = TreeNode(3, left=node4, right=node5)node1 = TreeNode(5, left=node2, right=node3)root = node1sln = ElegantSolution(root)print_successor(sln.successor(node6))  # 2print_successor(sln.successor(node2))  # 4print_successor(sln.successor(node5))  # 5print_successor(sln.successor(node3))  # Noneif __name__ == '__main__':main()

2.3 复杂度

  • 时间复杂度: O ( n ) O(n) O(n) ,因为要先遍历所有节点得到中序遍历序列;
  • 控件复杂度: O ( n ) O(n) O(n) ,因此至少需要一个实例属性 _nodes 来保存所有节点。

3. 解法二(迭代中序遍历)

3.1 分析

  • 作者: LeetCode

二叉搜索树的中序后继是中序遍历中当前节点之后 val 最小的节点。

可以分成两种情况来讨论:

  • 如果当前节点有右子节点的话,中序后继在当前节点之下,如下图中红色节点所示;
  • 如果当前节点没有右子节点的话,中序后继在当前节点之上,如下图中蓝色节点所示。

在这里插入图片描述
如果是下图这种情况,即当前节点有右子节点,找到顺序后继很简单,先找到当前节点的右孩子,然后再持续往左直到左孩子为空。

在这里插入图片描述

下面再来看一个复杂一点的情况,即当前节点无右子节点,这时候由于无法访问父亲节点,只能从根节点开始中序遍历。中序遍历通常由有递归和迭代的实现方式,这里用迭代的实现方式会更好一点。

直接在中序遍历过程保存前一个访问的节点,判断前一个节点是否为 p,如果是的话当前节点就是 p 节点的顺序后继。
在这里插入图片描述
中序遍历方法的时间复杂度为 O ( h ) O(h) O(h) ,其中 h h h 为树的高度。在第一种情况下也可以用中序遍历的方法,但之前的方法更快一点。

3.2 实现

from typing import Optionalclass TreeNode:def __init__(self, val: int, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def inorder_successor(self, root: TreeNode, p: TreeNode) -> Optional[TreeNode]:if root is None or not isinstance(root, TreeNode):returnif p.right:node = p.rightwhile node.left:node = node.leftreturn nodestack, prev = [], float('-inf')cursor = rootwhile True:if cursor:stack.append(cursor)cursor = cursor.leftelif stack:node = stack.pop()if prev == p.val:return nodeprev = node.valcursor = node.rightelse:breakreturndef print_successor(suc: TreeNode):if suc:print(suc.val)else:print(None)def main():node6 = TreeNode(1)node5 = TreeNode(4)node4 = TreeNode(2, left=node6)node3 = TreeNode(6)node2 = TreeNode(3, left=node4, right=node5)node1 = TreeNode(5, left=node2, right=node3)root = node1sln = Solution()print_successor(sln.inorder_successor(root, node6))  # 2print_successor(sln.inorder_successor(root, node2))  # 4print_successor(sln.inorder_successor(root, node5))  # 5print_successor(sln.inorder_successor(root, node3))  # Noneif __name__ == '__main__':main()

3.3 复杂度

  • 时间复杂度: 如果节点 p 有右子节点,时间复杂度为 O ( h p ) O(h_p) O(hp) ,其中 O ( h p ) O(h_p) O(hp) 是节点 p 的高度。如果没有右子节点,时间复杂度为 O ( H ) O(H) O(H),其中 h h h 为树的高度;
  • 空间复杂度: 如果节点 p 有右子节点,空间复杂度为 O ( 1 ) O(1) O(1) 。如果没有右子节点,空间复杂度度为 O ( h ) O(h) O(h)

实际上,上述迭代解法并没有充分利用给定的是一棵二叉搜索树这一个条件,如果利用这个条件,上述的迭代实现可以进一步优化如下:

from typing import Optionalclass TreeNode:def __init__(self, val: int, left=None, right=None):self.val = valself.left = leftself.right = rightclass Solution:def inorder_successor(self, root: TreeNode, p: TreeNode) -> Optional[TreeNode]:if root is None or not isinstance(root, TreeNode):returnif p.right:node = p.rightwhile node.left:node = node.leftreturn nodesuccessor = Nonewhile root:if root.val < p.val:root = root.rightelif root.val > p.val:successor = rootroot = root.leftelse:breakreturn successor

上述实现进一步将空间复杂度降低到了 O ( 1 ) O(1) O(1)


http://www.ppmy.cn/news/663413.html

相关文章

285. 二叉搜索树中的中序后继

285. 二叉搜索树中的中序后继&#xff1a; 题目链接 &#xff1a;285. 二叉搜索树中的中序后继 题目&#xff1a; 给定一棵二叉搜索树和其中的一个节点 p &#xff0c;找到该节点在树中的中序后继。如果节点没有中序后继&#xff0c;请返回 null 。 节点 p 的后继是值比 p.va…

python输出结果的个数_下列Python语句的输出结果是 print(数量{0},单价{1}.format(100,285.6)) print(str.format(数量{0},单价{1:3...

【简答题】How can a lack of critical thinking cause a loss of personal freedom? 【多选题】Python中内置的4种数据类型为 ____________________________________, 【简答题】下列Python语句的运行结果是 x=False; y=True; z=False if x or y and z: print("yes"…

LeetCode 285. 二叉搜索树中的中序后继

具体思路&#xff1a; 直接遍历找pre->valroot->val的情况&#xff1b; 具体代码&#xff1a; /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode(int x) : val(x), left(NULL), …

S7-1200使用集成库FB285控制G120变频器的基本步骤

S7-1200使用集成库FB285控制G120变频器的基本步骤 硬件: CPU:1211 变频器:CU250S-2PN 软件: PROFINET连接,使用标准报文1 安装 StartDrive 软件或Drivelib程序库后,在博途软件即可使用驱动库文件。 Drivelib下载链接:(适用于S71-200和S7-1500) https://support.ind…

刑法285条非法获取计算机信息数据,刑法285条量刑标准,提供侵入计算机系统工具罪,并被拘役...

是指提供专门用于侵入、非法控制计算机信息系统的程序、工具&#xff0c;或者明知他人实施侵入、非法控制计算机信息系统的违法犯罪行为而为其提供程序、工具&#xff0c;情节严重的行为。 构成要件&#xff1a; 1、侵犯的客体&#xff1a;是国家信息网络的安全。 2、客观方面&…

AtCoder Beginner Contest 285 青大蒟蒻训练日常(A-F) 上分场(可惜unr)

比赛链接 A Edge Checker 2 线段树上判断a , b有边无边 &#xff0c; 先把层数低的放在a上&#xff0c;判断b/2 a即可 提交 B Longest Uncommon Prefix 暴力 提交 C abc285_brutmhyhiizp 先把长度短的全部加上去&#xff0c;剩下的就转化为26进制去做即可&#xff0c;然后加上…

【CS285 深度强化学习 】作业一之详解 [Deep Reinforcement Learning]

目录 前情提要与引用参考&#xff1a;顺序阅读代码&#xff1a;BC_Trainer**BCAgent**MLP_policy.py ReplayBufferRL_Trainercollect_training_trajectoriesdo_relabel_with_expert 代码完成后的结果分析BC Behaviour cloningQ1.3分析训练成果对比 问题合集numpy.core._excepti…

285

论文阅读备份