Smallest String Starting From Leaf - Pre-Order DFS
Problem: https://leetcode.com/problems/smallest-string-starting-from-leaf/description/
The problem can actually be solved with a DFS (Depth-First-Search) and pre-order traversal:
Given the
root of a binary tree, each node has a value from 0 to 25 representing the letters 'a' to 'z': a value of 0 represents 'a', a value of 1 represents 'b', and so on.
Find the lexicographically smallest string that starts at a leaf of this tree and ends at the root.
(As a reminder, any shorter prefix of a string is lexicographically smaller: for example,
"ab" is lexicographically smaller than "aba". A leaf of a node is a node that has no children.)
Example 1:

Input: [0,1,2,3,4,3,4] Output: "dba"
Example 2:

Input: [25,1,3,1,3,0,2] Output: "adz"
Example 3:

Input: [2,2,1,null,1,0,null,0] Output: "abc"
Note:
- The number of nodes in the given tree will be between
1and1000. - Each node in the tree will have a value between
0and25.
- If you land on a null, return and do nothing
- Landing on a leaf, process the case checking whether this solution is better than the best
- Call Left
- Call Right
Code is below, cheers, ACC.
public class Solution
{
public string SmallestFromLeaf(TreeNode root)
{
string smallest = "";
SmallestFromLeaf(root, "", ref smallest);
return smallest;
}
private void SmallestFromLeaf(TreeNode node,
string current,
ref string smallest)
{
if (node == null) return;
if (node.left == null && node.right == null)
{
current = ((char)(node.val + (int)'a')).ToString() + current;
if (!String.IsNullOrEmpty(current) && (String.IsNullOrEmpty(smallest) || current.CompareTo(smallest) < 0))
{
smallest = current;
}
return;
}
SmallestFromLeaf(node.left, ((char)(node.val + (int)'a')).ToString() + current, ref smallest);
SmallestFromLeaf(node.right, ((char)(node.val + (int)'a')).ToString() + current, ref smallest);
}
}
Nice, although I think the reason why it takes so long to execute is because of unnecessary string conversions in each invocation - it's possible and faster to compare ints instead of strings, not to mention the allocation cost. It's also possible to use a sentinel value (any int that is larger than 26) to avoid extra String.IsNullOrEmpty(smallest) checks. My initial take was using explicit stack:
ReplyDeleteclass Solution:
def smallestFromLeaf(self, root: TreeNode) -> str:
smallest_so_far: 'List[Tuple[int]]' = [(27,)] # sentinel larger than any lowercase letter
def explore(node: TreeNode, stack: 'List[int]') -> None:
stack.append(node.val)
if not node.left and not node.right: # leaf
smallest_so_far[0] = min(smallest_so_far[0], tuple(reversed(stack)))
else:
for child in (node.left, node.right):
if child:
explore(child, stack)
stack.pop()
explore(root, [])
return "".join(chr(val + ord('a')) for val in smallest_so_far[0])
https://gist.github.com/ttsugriy/d55bfffdeef86989c2568a0c4cd131ef
but using implicit stack makes the solution much more concise:
class Solution:
def smallestFromLeaf(self, root: TreeNode) -> str:
if not root:
return "{" # sentinel larger than any lowercase letter
string = chr(root.val + ord('a'))
return string if root.left == root.right else min(self.smallestFromLeaf(root.left) + string, self.smallestFromLeaf(root.right) + string)
https://gist.github.com/ttsugriy/8b3048ba99685bc1507abf3220870068
Even though both solutions are in Python they are significantly faster than C# (~40ms) and use much less memory ~6.8MB :)
Yeah, totally true, there is a lot of unnecessary string conversion, thanks for pointing that out!
ReplyDeletewith good JIT it's probably not that much of a difference, but I was just surprised to see that Python is taking less memory :)
Delete