Maximum Width of Binary Tree in DSA Rust - Time & Space Complexity
We want to understand how the time needed to find the maximum width of a binary tree changes as the tree grows.
Specifically, how does the number of nodes affect the work done?
Analyze the time complexity of the following code snippet.
use std::collections::VecDeque;
fn width_of_binary_tree(root: Option<&Box>) -> usize {
if root.is_none() { return 0; }
let mut max_width = 0;
let mut queue = VecDeque::new();
queue.push_back((root.unwrap(), 0));
while !queue.is_empty() {
let level_length = queue.len();
let first_index = queue.front().unwrap().1;
let mut last_index = 0;
for _ in 0..level_length {
let (node, idx) = queue.pop_front().unwrap();
let curr_index = idx - first_index;
if let Some(left) = &node.left {
queue.push_back((left, 2 * curr_index + 1));
}
if let Some(right) = &node.right {
queue.push_back((right, 2 * curr_index + 2));
}
last_index = curr_index;
}
max_width = max_width.max(last_index + 1);
}
max_width
}
struct TreeNode {
val: i32,
left: Option>,
right: Option>,
}
This code finds the maximum width of a binary tree by level-order traversal using a queue.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: The while loop that visits each node once.
- How many times: Each node is processed exactly once in the queue.
As the number of nodes (n) increases, the code visits each node once, so the work grows directly with n.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 visits to nodes |
| 100 | About 100 visits to nodes |
| 1000 | About 1000 visits to nodes |
Pattern observation: The operations grow linearly as the tree size grows.
Time Complexity: O(n)
This means the time to find the maximum width grows in direct proportion to the number of nodes in the tree.
[X] Wrong: "The time depends on the height of the tree only."
[OK] Correct: The code visits every node, not just the height levels, so the total nodes matter more than height alone.
Understanding this helps you explain how breadth-first search works and why visiting all nodes is necessary to find maximum width.
"What if we used depth-first search instead of breadth-first search? How would the time complexity change?"
Practice
Solution
Step 1: Understand the definition of width in a binary tree
The width of a binary tree at a level is the count of nodes present at that level.Step 2: Identify maximum width
The maximum width is the largest such count among all levels.Final Answer:
The largest number of nodes present at any single level of the tree -> Option CQuick Check:
Width = max nodes at any level [OK]
- Confusing width with tree height
- Thinking width is sum of node values
- Mixing width with total nodes count
Solution
Step 1: Identify the need for level order traversal
To find maximum width, we visit nodes level by level, which is a breadth-first search (BFS).Step 2: Choose data structure for BFS
BFS uses a queue to process nodes in FIFO order. Rust's VecDeque supports efficient push/pop from both ends.Final Answer:
VecDeque (double-ended queue) -> Option AQuick Check:
Use queue (VecDeque) for BFS traversal [OK]
- Using Vec which is inefficient for queue operations
- Using HashMap which is unordered
- Using BTreeSet which is for sorted unique elements
struct TreeNode {
val: i32,
left: Option,
right: Option,
}
fn max_width(root: Option) -> usize {
use std::collections::VecDeque;
if root.is_none() { return 0; }
let mut queue = VecDeque::new();
queue.push_back(root.unwrap());
let mut max_width = 0;
while !queue.is_empty() {
let level_size = queue.len();
max_width = max_width.max(level_size);
for _ in 0..level_size {
let node = queue.pop_front().unwrap();
if let Some(left) = node.left {
queue.push_back(left);
}
if let Some(right) = node.right {
queue.push_back(right);
}
}
}
max_width
}
fn main() {
let root = Some(Box::new(TreeNode {
val: 1,
left: Some(Box::new(TreeNode { val: 2, left: None, right: None })),
right: Some(Box::new(TreeNode {
val: 3,
left: Some(Box::new(TreeNode { val: 4, left: None, right: None })),
right: None,
})),
}));
println!("{}", max_width(root));
}Solution
Step 1: Analyze the tree structure
Level 1: Node 1 (1 node) Level 2: Nodes 2 and 3 (2 nodes) Level 3: Node 4 (1 node)Step 2: Calculate maximum width
Widths per level: 1, 2, 1 Maximum width is 2.Final Answer:
2 -> Option BQuick Check:
Max width = 2 nodes at level 2 [OK]
- Counting total nodes instead of per level
- Ignoring empty children in width calculation
- Confusing node values with count
fn max_width(root: Option) -> usize {
use std::collections::VecDeque;
if root.is_none() { return 0; }
let mut queue = VecDeque::new();
queue.push_back(root.unwrap());
let mut max_width = 0;
while !queue.is_empty() {
let level_size = queue.len();
max_width = max_width.max(level_size);
for _ in 0..level_size {
let node = queue.pop_front().unwrap();
if node.left.is_some() {
queue.push_back(node.left.unwrap());
}
if node.right.is_some() {
queue.push_back(node.right.unwrap());
}
}
}
max_width
}Solution
Step 1: Identify unsafe unwrap calls
Calling unwrap() on node.left or node.right without safely extracting causes panic if None.Step 2: Correct approach
Use if let Some(left) = node.left { queue.push_back(left); } to safely handle Options.Final Answer:
Calling unwrap() on Option without checking causes panic -> Option DQuick Check:
Unsafe unwrap causes runtime panic [OK]
- Using unwrap() directly on Option fields
- Ignoring empty tree check (though present here)
- Confusing VecDeque with Vec performance
use std::collections::VecDeque;
fn max_width_with_positions(root: Option) -> usize {
if root.is_none() { return 0; }
let mut max_width = 0;
let mut queue = VecDeque::new();
queue.push_back((root.unwrap(), 0usize));
while !queue.is_empty() {
let level_length = queue.len();
let first_index = queue.front().unwrap().1;
let last_index;
for i in 0..level_length {
let (node, idx) = queue.pop_front().unwrap();
let idx = idx - first_index; // normalize to prevent overflow
if node.left.is_some() {
queue.push_back((node.left.unwrap(), 2 * idx + 1));
}
if node.right.is_some() {
queue.push_back((node.right.unwrap(), 2 * idx + 2));
}
if i == level_length - 1 {
last_index = idx;
}
}
max_width = max_width.max(last_index + 1);
}
max_width
}Solution
Step 1: Understand position indexing
Assigning indices as if complete binary tree helps measure width including gaps.Step 2: Normalize indices to prevent overflow
Subtracting first index at each level keeps indices small and prevents usize overflow.Step 3: Calculate width as last_index - first_index + 1
Using normalized indices, width = last_index + 1 at each level.Final Answer:
Normalize indices by subtracting first index at each level to avoid overflow -> Option AQuick Check:
Index normalization prevents overflow and calculates width correctly [OK]
- Not normalizing indices causing overflow
- Ignoring gaps by counting nodes only
- Using depth instead of position indices
Ready to prove it?
Pass the test and this pattern moves unknown
