BST Property and Why It Matters in DSA Rust - Time & Space Complexity
We want to understand how the special rule of a Binary Search Tree (BST) affects how fast we can find or add items.
How does this rule help us answer: "How long will it take to search or insert?"
Analyze the time complexity of the following code snippet.
struct Node {
value: i32,
left: Option>,
right: Option>,
}
fn search_bst(root: &Option>, target: i32) -> bool {
match root {
None => false,
Some(node) => {
if node.value == target {
true
} else if target < node.value {
search_bst(&node.left, target)
} else {
search_bst(&node.right, target)
}
}
}
}
This code searches for a value in a BST by using the BST rule to decide which side to search next.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Recursive calls moving down one node at a time.
- How many times: At most once per tree level until the value is found or no nodes left.
Each step moves down one level of the tree, cutting the search space roughly in half if the tree is balanced.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 4 steps (tree height) |
| 100 | About 7 steps |
| 1000 | About 10 steps |
Pattern observation: The steps grow slowly as input grows, because the tree height grows roughly with the logarithm of n.
Time Complexity: O(log n)
This means searching or inserting takes time proportional to the height of the tree, which is much faster than checking every item.
[X] Wrong: "Searching a BST always takes the same time as a simple list, because you still check nodes one by one."
[OK] Correct: The BST rule lets you skip half the tree at each step, so you don't check every node, making it much faster.
Understanding how the BST property speeds up search helps you explain why data structures matter and shows you can reason about efficiency clearly.
"What if the BST becomes a linked list (all nodes only have one child)? How would the time complexity change?"
Practice
Solution
Step 1: Understand BST definition
A BST requires that for every node, values in the left subtree are smaller and values in the right subtree are larger.Step 2: Compare options to BST property
For each node, all values in the left subtree are smaller, and all values in the right subtree are larger. matches this definition exactly, while others describe different tree types or properties.Final Answer:
For each node, all values in the left subtree are smaller, and all values in the right subtree are larger. -> Option AQuick Check:
BST property = For each node, all values in the left subtree are smaller, and all values in the right subtree are larger. [OK]
- Confusing BST with complete binary tree
- Thinking all nodes must have two children
- Assuming leaf nodes must be at same depth
Solution
Step 1: Understand Rust ownership and recursive types
Recursive structs in Rust must use Box to allocate on heap and Option for nullable children.Step 2: Check each option
struct Node { value: i32, left: Option, right: Option } uses Option correctly. Using Node directly or Option<Node> (without Box) causes infinite size. struct Node { value: i32, left: Box<Node>, right: Box<Node> } misses Option, so children can't be null.Final Answer:
struct Node { value: i32, left: Option, right: Option } -> Option BQuick Check:
Use Option for nullable recursive fields [OK]
- Omitting Option for nullable children
- Using Node directly causing infinite size error
- Not boxing recursive fields
struct Node { value: i32, left: Option, right: Option }
fn insert(root: &mut Option, val: i32) {
match root {
None => *root = Some(Box::new(Node { value: val, left: None, right: None })),
Some(node) => {
if val < node.value {
insert(&mut node.left, val);
} else {
insert(&mut node.right, val);
}
}
}
}
fn inorder(root: &Option, vals: &mut Vec<i32>) {
if let Some(node) = root {
inorder(&node.left, vals);
vals.push(node.value);
inorder(&node.right, vals);
}
}
fn main() {
let mut root = None;
insert(&mut root, 10);
insert(&mut root, 5);
insert(&mut root, 15);
insert(&mut root, 7);
let mut result = Vec::new();
inorder(&root, &mut result);
println!("{:?}", result);
}Solution
Step 1: Insert values into BST
Insert 10 as root, 5 to left, 15 to right, 7 to right of 5.Step 2: Perform in-order traversal
In-order visits left subtree, node, then right subtree. So order is 5, 7, 10, 15.Final Answer:
[5, 7, 10, 15] -> Option AQuick Check:
In-order traversal of BST = sorted values [OK]
- Confusing traversal order
- Inserting nodes incorrectly
- Assuming pre-order or post-order output
fn is_bst(node: &Option, min: i32, max: i32) -> bool {
match node {
None => true,
Some(n) => {
if n.value <= min || n.value >= max {
return false;
}
is_bst(&n.left, min, n.value) && is_bst(&n.right, n.value, max)
}
}
}Solution
Step 1: Analyze comparison operators
The function uses <= and >= which disallow equal values, meaning duplicates are not allowed.Step 2: Understand BST duplicate handling
Some BST definitions allow duplicates on one side (usually right). Using strict < and > allows duplicates on one side.Final Answer:
The comparison uses <= and >= instead of < and >, disallowing duplicate values. -> Option DQuick Check:
Use < and > to allow duplicates in BST check [OK]
- Thinking None nodes cause errors
- Believing min/max are updated incorrectly
- Assuming empty tree is invalid
Solution
Step 1: Recall BST in-order traversal property
In-order traversal visits nodes in ascending order due to BST property.Step 2: Use in-order traversal to find kth smallest
Counting nodes during in-order traversal lets you stop at kth smallest efficiently without extra sorting.Final Answer:
Perform an in-order traversal and return the kth element visited. -> Option CQuick Check:
In-order traversal gives sorted order, find kth element [OK]
- Using pre-order or level-order which are not sorted
- Converting to array wastes extra space
- Not leveraging BST property for efficiency
Ready to prove it?
Pass the test and this pattern moves unknown
