leetcode
leetcode 901 ~ 950
从叶结点开始的最小字符串

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

难度:

标签:

题目描述

给定一颗根结点为 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

代码结果

运行时间: 26 ms, 内存: 17.3 MB


/*
 * 思路:
 * 1. 使用递归函数处理每个结点,将叶结点到根结点的字符串加入一个列表。
 * 2. 使用Java Stream API对列表进行排序,并返回按字典序最小的字符串。
 */

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

class Solution {
    public String smallestFromLeaf(TreeNode root) {
        List<String> paths = new ArrayList<>();
        buildPaths(root, new StringBuilder(), paths);
        return paths.stream().sorted().findFirst().orElse("");
    }

    private void buildPaths(TreeNode node, StringBuilder sb, List<String> paths) {
        if (node == null) return;
        sb.append((char) ('a' + node.val));
        if (node.left == null && node.right == null) {
            sb.reverse();
            paths.add(sb.toString());
            sb.reverse();
        }
        buildPaths(node.left, sb, paths);
        buildPaths(node.right, sb, paths);
        sb.deleteCharAt(sb.length() - 1);
    }
}

解释

方法:

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

时间复杂度:

O(n * h)

空间复杂度:

O(h) or O(n)

代码细节讲解

🦆
为什么在遍历到叶子节点时需要将路径中的字符进行反转?
在这个题解中,从根节点到叶节点的路径是按照从根到叶的顺序添加到列表`path`中的。因此,列表`path`的内容是从根节点开始到叶节点结束的字符序列。但题目要求输出的是从叶节点开始到根节点的字符串。因此,当到达叶节点时,需要将`path`中的字符序列进行反转,以构造出从叶节点到根节点的字符串。这样反转后的字符串才符合题目要求的输出格式。
🦆
在递归函数`__traverse`中,如果当前已找到的最小字符串`min_dict_order_str`非空,为何还需要继续比较当前字符串`curr_str`?
尽管已经找到一个最小字符串`min_dict_order_str`,但在遍历过程中仍可能会遇到字典序更小的字符串。在二叉树的不同叶节点生成的字符串可能不同,并且其中一些可能会比当前已知的最小字符串更小。因此,每次到达叶节点生成一个新的字符串`curr_str`时,都需要与当前已知的最小字符串`min_dict_order_str`进行比较,以确保最终得到的是所有可能字符串中的最小字典序字符串。
🦆
在存储路径的列表`path`中直接添加字符而不是节点的值有什么优势?
在存储路径的列表`path`中直接添加字符而不是节点的值的主要优势在于简化了处理过程。如果存储的是节点的整数值,每次生成字符串时都需要将整数值转换为对应的字符。这不仅增加了计算的复杂性,还可能增加了代码的执行时间。通过直接存储字符,可以在构建路径的同时完成转换,使得生成最终字符串时更为直接和高效。
🦆
递归函数`__traverse`中的`if not root: return`是必要的吗,考虑到调用这个函数之前是否有检查节点是否存在?
在递归函数`__traverse`中的`if not root: return`是一个重要的安全检查,它确保了当递归到树的空子节点时函数能够正确返回,而不是继续执行下去引发错误。虽然在递归调用前通常会检查子节点是否存在,但这个检查是必要的。它处理了边缘情况,例如树完全为空的情况(即,根节点为`None`的情况)。此外,这种检查还可以增强代码的健壮性,避免在未来修改代码或者在不同情景下复用时出现问题。

相关问题

求根节点到叶节点数字之和

给你一个二叉树的根节点 root ,树中每个节点都存放有一个 09 之间的数字。

每条从根节点到叶节点的路径都代表一个数字:

  • 例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123

计算从根节点到叶节点生成的 所有数字之和

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

 

示例 1:

输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25

示例 2:

输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026

 

提示:

  • 树中节点的数目在范围 [1, 1000]
  • 0 <= Node.val <= 9
  • 树的深度不超过 10

二叉树的所有路径

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

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

 

示例 1:

输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]

示例 2:

输入:root = [1]
输出:["1"]

 

提示:

  • 树中节点的数目在范围 [1, 100]
  • -100 <= Node.val <= 100