A clear explanation of verifying preorder serialization using slot counting without reconstructing the tree.
Problem Restatement
We are given a string preorder.
The string represents a preorder traversal serialization of a binary tree.
Each token is separated by a comma.
A non-null node is written as an integer.
A null child is written as #.
For example:
"9,3,4,#,#,1,#,#,2,#,6,#,#"We need to return true if this string is a valid preorder serialization of some binary tree.
We are not allowed to reconstruct the tree. The input format itself is valid, so we do not need to handle cases like "1,,3".
Input and Output
| Item | Meaning |
|---|---|
| Input | A comma-separated preorder string |
| Output | true if it is a valid serialization, otherwise false |
| Null marker | # |
| Non-null node | An integer |
| Restriction | Do not rebuild the tree |
Function shape:
def isValidSerialization(preorder: str) -> bool:
...Examples
Example 1:
Input: preorder = "9,3,4,#,#,1,#,#,2,#,6,#,#"
Output: trueThis is a valid serialization.
The root is 9.
Its left subtree is:
3,4,#,#,1,#,#Its right subtree is:
2,#,6,#,#Every non-null node has exactly two child slots, and every slot is filled.
Example 2:
Input: preorder = "1,#"
Output: falseThe node 1 has a left child #, but its right child is missing.
So the serialization is incomplete.
Example 3:
Input: preorder = "9,#,#,1"
Output: falseThe part:
9,#,#already forms a complete tree.
The extra 1 has no available parent slot.
So the serialization is invalid.
First Thought: Rebuild the Tree
One possible idea is to parse the preorder string and recursively build the tree.
For each non-null node, build its left child and right child.
For each #, return a null node.
But the problem explicitly says we should not reconstruct the tree.
We only need to verify structure.
So we should track how many child positions are available.
Key Insight
Think of the serialization as filling empty slots.
At the start, there is one slot available for the root.
slots = 1Each token consumes one slot.
If the token is #, it consumes a slot and creates no new slots.
If the token is a number, it consumes one slot but creates two child slots.
So for a non-null node, the net change is:
-1 + 2 = +1For a null node, the net change is:
-1At any point, if we need to consume a slot but there are no slots left, the serialization is invalid.
At the end, every slot must be filled, so slots must be exactly 0.
Algorithm
Split preorder by commas.
Set:
slots = 1Then scan each token from left to right.
For each token:
- Consume one slot.
- If
slotsbecomes negative, returnFalse. - If the token is not
#, add two slots. - Continue.
At the end, return whether slots == 0.
Walkthrough
Take:
preorder = "9,#,#"Start:
slots = 1Read 9.
It consumes one slot:
slots = 0It is a non-null node, so it creates two child slots:
slots = 2Read first #.
It consumes one slot:
slots = 1Read second #.
It consumes one slot:
slots = 0End of input.
All slots are filled, so the answer is true.
Now take:
preorder = "1,#"Start:
slots = 1Read 1.
Consume one slot and create two:
slots = 2Read #.
Consume one slot:
slots = 1End of input.
One slot remains unfilled, so the answer is false.
Correctness
A valid binary tree serialization must fill exactly one root slot at the beginning.
Every token in preorder fills exactly one currently available slot.
A null marker # fills one slot and creates no children.
A non-null node fills one slot and creates two new child slots.
The algorithm models this process exactly.
If slots becomes negative, then the serialization tries to place a node after all available child positions have already been filled. No binary tree can have such a serialization.
If the scan ends with slots > 0, then some child positions were never filled. The serialization is incomplete.
If the scan ends with slots == 0, then every created child position was filled exactly once, and no token appeared after the tree was complete. Therefore the serialization represents a valid binary tree.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan each token once |
| Space | O(n) | split(",") creates a token list |
Here, n is the length of the input string.
We can reduce extra space by scanning the string manually, but split is simpler and accepted.
Implementation
class Solution:
def isValidSerialization(self, preorder: str) -> bool:
slots = 1
for node in preorder.split(","):
slots -= 1
if slots < 0:
return False
if node != "#":
slots += 2
return slots == 0Code Explanation
We begin with one available slot:
slots = 1This slot is for the root.
For every token:
for node in preorder.split(","):we consume one slot:
slots -= 1If no slot was available, the serialization is invalid:
if slots < 0:
return FalseIf the token is a real node, it creates two child slots:
if node != "#":
slots += 2At the end:
return slots == 0The serialization is valid only when all slots have been filled.
Testing
def run_tests():
s = Solution()
assert s.isValidSerialization("9,3,4,#,#,1,#,#,2,#,6,#,#") == True
assert s.isValidSerialization("1,#") == False
assert s.isValidSerialization("9,#,#,1") == False
assert s.isValidSerialization("#") == True
assert s.isValidSerialization("#,#") == False
assert s.isValidSerialization("1,#,#") == True
assert s.isValidSerialization("1,2,#,#,#") == True
assert s.isValidSerialization("1,2,#,#") == False
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
| Full sample | Valid larger tree |
"1,#" | Missing right child |
"9,#,#,1" | Extra node after complete tree |
"#" | Empty tree |
"#,#" | Extra null after complete tree |
"1,#,#" | Single root with two null children |
"1,2,#,#,#" | Valid tree with left child |
"1,2,#,#" | Missing root right child |