Why Stack Exists and What Problems It Solves in DSA Rust - Performance Analysis
We want to understand why the stack data structure is useful and how fast it works for common tasks.
What question are we trying to answer? How quickly can we add, remove, or check items using a stack?
Analyze the time complexity of the following code snippet.
struct Stack {
items: Vec,
}
impl<T> Stack<T> {
fn push(&mut self, item: T) {
self.items.push(item);
}
fn pop(&mut self) -> Option<T> {
self.items.pop()
}
fn peek(&self) -> Option<&T> {
self.items.last()
}
}
This code defines a stack with push, pop, and peek operations using a vector.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Adding or removing an item at the end of the vector (push/pop).
- How many times: Each push or pop happens once per call, no loops inside these methods.
Each push or pop takes about the same time no matter how many items are in the stack.
| Input Size (n) | Approx. Operations per push/pop |
|---|---|
| 10 | 1 |
| 100 | 1 |
| 1000 | 1 |
Pattern observation: The time to add or remove an item stays constant even as the stack grows.
Time Complexity: O(1)
This means adding or removing an item from the stack takes the same short time no matter how many items are inside.
[X] Wrong: "Removing an item from the stack takes longer as the stack grows because it has more items."
[OK] Correct: The stack only adds or removes items at one end, so it does not need to look through all items. Each operation is quick and does not depend on stack size.
Understanding stack operations and their constant time behavior helps you explain why stacks are great for tasks like undo features, parsing, and managing function calls.
"What if we changed the stack to add and remove items from the start of the vector? How would the time complexity change?"
Practice
Solution
Step 1: Understand the role of stack in function calls
Stacks help keep track of where each function should return after it finishes executing, by storing return addresses.Step 2: Compare with other options
Stacks do not sort data automatically, nor store data randomly or permanently; their main use is managing function calls.Final Answer:
To keep track of function calls and return addresses -> Option AQuick Check:
Stack = function call tracking [OK]
- Thinking stack sorts data automatically
- Confusing stack with permanent storage
- Assuming stack stores data randomly
Solution
Step 1: Recall Rust vector methods for stack operations
In Rust, a vector's push() method adds an element to the end, simulating stack push.Step 2: Check other options for correctness
Methods add(), insert(), and append() do not exist or do not behave like push() for stack usage.Final Answer:
stack.push(element); -> Option DQuick Check:
Use push() to add element to stack [OK]
- Using add() which is not a Rust Vec method
- Confusing insert() which needs index
- Using append() which merges vectors
let mut stack = Vec::new();
stack.push(10);
stack.push(20);
let top = stack.pop();
println!("{:?}", top);Solution
Step 1: Trace stack push operations
First 10 is pushed, then 20 is pushed. Stack now has [10, 20].Step 2: Understand pop() behavior
pop() removes and returns the last element, which is 20, wrapped in Some(20) because pop returns Option.Final Answer:
Some(20) -> Option AQuick Check:
pop() returns last element wrapped in Some [OK]
- Expecting pop() to return raw value, not Option
- Confusing order of elements pushed
- Thinking pop() returns None when stack is not empty
let mut stack = Vec::new();
let top = stack.pop().unwrap();
println!("{}", top);Solution
Step 1: Analyze pop() on empty vector
pop() returns None if vector is empty. Calling unwrap() on None causes panic (runtime error).Step 2: Check other options for validity
pop() exists on Vec, stack is mutable, and println! syntax is correct.Final Answer:
Calling unwrap() on empty stack causes panic -> Option BQuick Check:
unwrap() on None panics [OK]
- Ignoring possibility of empty stack
- Thinking pop() is undefined on Vec
- Assuming println! syntax is wrong
let list = vec![1, 2, 3, 4];
let mut stack = Vec::new();
// Fill stack
for &item in &list {
stack.push(item);
}
// Extract reversed list
let mut reversed = Vec::new();
while let Some(val) = stack.pop() {
reversed.push(val);
}
println!("{:?}", reversed);Solution
Step 1: Understand pushing all elements onto stack
Each element from list is pushed onto stack in order: 1,2,3,4.Step 2: Popping elements reverses order
pop() removes last pushed element first, so reversed vector gets 4,3,2,1.Step 3: Confirm no panic occurs
while let Some(val) safely handles empty stack, no panic occurs.Final Answer:
This code correctly reverses the list using a stack -> Option CQuick Check:
Stack pop reverses order [OK]
- Replacing pop() with peek() which doesn't remove
- Assuming code panics on empty stack
- Ignoring that stack reverses order naturally
Ready to prove it?
Pass the test and this pattern moves unknown
