从叶结点开始的最小字符串

标签: 深度优先搜索 字符串 二叉树

难度: Medium

给定一颗根结点为 root 的二叉树,树中的每一个结点都有一个 [0, 25] 范围内的值,分别代表字母 'a' 到 'z'

返回 按字典序最小 的字符串,该字符串从这棵树的一个叶结点开始,到根结点结束

字符串中任何较短的前缀在 字典序上 都是 较小 的:

  • 例如,在字典序上 "ab" 比 "aba" 要小。叶结点是指没有子结点的结点。 

节点的叶节点是没有子节点的节点。

示例 1:

输入:root = [0,1,2,3,4,3,4]
输出:"dba"

示例 2:

输入:root = [25,1,3,1,3,0,2]
输出:"adz"

示例 3:

输入:root = [2,2,1,null,1,0,null,0]
输出:"abc"

提示:

  • 给定树的结点数在 [1, 8500] 范围内
  • 0 <= Node.val <= 25

Submission

运行时间: 26 ms

内存: 17.3 MB

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def smallestFromLeaf(self, root: Optional[TreeNode]) -> str:
        ord_a = ord('a')

        path = []
        min_dict_order_str = ""
        def __traverse(root):
            nonlocal min_dict_order_str
            if not root: return
            alp_char = chr(root.val + ord_a)
            path.append(alp_char)
            if not (root.left or root.right):
                curr_str = ''.join(path[::-1])
                if not min_dict_order_str or curr_str < min_dict_order_str:
                    min_dict_order_str = curr_str
            else:
                __traverse(root.left)
                __traverse(root.right)
            path.pop()
        
        __traverse(root)
        return min_dict_order_str

Explain

这个题解采用了DFS(深度优先搜索)的方法来遍历整个二叉树。在遍历的过程中,使用一个列表 `path` 来存储从根节点到当前节点的路径。当到达一个叶节点(即没有子节点的节点)时,将 `path` 中的字符反转并连接成字符串,以得到从叶节点到根节点的字符串。然后,比较当前生成的字符串与先前找到的最小字符串,如果当前的更小,则更新结果字符串。这个过程持续进行,直到所有的叶节点都被访问过。最后,返回字典序最小的字符串。

时间复杂度: O(n * h)

空间复杂度: O(h) or O(n)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def smallestFromLeaf(self, root: Optional[TreeNode]) -> str:
        ord_a = ord('a')  # ASCII value of 'a'

        path = []  # This will store the current path from root to the current node
        min_dict_order_str = ""  # This will store the smallest lexicographical string found
        def __traverse(root):
            nonlocal min_dict_order_str
            if not root: return
            alp_char = chr(root.val + ord_a)  # Convert the node value to corresponding character
            path.append(alp_char)
            if not (root.left or root.right):  # Check if it is a leaf node
                curr_str = ''.join(path[::-1])  # Reverse the path to form the required string
                if not min_dict_order_str or curr_str < min_dict_order_str:  # Compare with the smallest string found so far
                    min_dict_order_str = curr_str
            else:
                __traverse(root.left)
                __traverse(root.right)
            path.pop()  # Backtrack
        
        __traverse(root)
        return min_dict_order_str

Explore

在这个题解中,从根节点到叶节点的路径是按照从根到叶的顺序添加到列表`path`中的。因此,列表`path`的内容是从根节点开始到叶节点结束的字符序列。但题目要求输出的是从叶节点开始到根节点的字符串。因此,当到达叶节点时,需要将`path`中的字符序列进行反转,以构造出从叶节点到根节点的字符串。这样反转后的字符串才符合题目要求的输出格式。

尽管已经找到一个最小字符串`min_dict_order_str`,但在遍历过程中仍可能会遇到字典序更小的字符串。在二叉树的不同叶节点生成的字符串可能不同,并且其中一些可能会比当前已知的最小字符串更小。因此,每次到达叶节点生成一个新的字符串`curr_str`时,都需要与当前已知的最小字符串`min_dict_order_str`进行比较,以确保最终得到的是所有可能字符串中的最小字典序字符串。

在存储路径的列表`path`中直接添加字符而不是节点的值的主要优势在于简化了处理过程。如果存储的是节点的整数值,每次生成字符串时都需要将整数值转换为对应的字符。这不仅增加了计算的复杂性,还可能增加了代码的执行时间。通过直接存储字符,可以在构建路径的同时完成转换,使得生成最终字符串时更为直接和高效。

在递归函数`__traverse`中的`if not root: return`是一个重要的安全检查,它确保了当递归到树的空子节点时函数能够正确返回,而不是继续执行下去引发错误。虽然在递归调用前通常会检查子节点是否存在,但这个检查是必要的。它处理了边缘情况,例如树完全为空的情况(即,根节点为`None`的情况)。此外,这种检查还可以增强代码的健壮性,避免在未来修改代码或者在不同情景下复用时出现问题。