Dynamic Stack Using Resizable Array in DSA Go - Time & Space Complexity
We want to understand how the time to add or remove items from a dynamic stack changes as the stack grows.
How does resizing the array affect the speed of stack operations?
Analyze the time complexity of the following dynamic stack push operation.
func (s *Stack) Push(value int) {
if s.size == len(s.array) {
newArray := make([]int, 2*len(s.array))
copy(newArray, s.array)
s.array = newArray
}
s.array[s.size] = value
s.size++
}
This code adds a value to the stack, doubling the array size when full.
Look at what repeats when pushing many items.
- Primary operation: Adding one item to the array (simple assignment).
- How many times: Once per push, except when resizing.
- Resizing operation: Copying all current items to a new larger array.
- How many times resizing happens: Only when the array is full, which happens less often as size grows.
Adding items usually takes a quick step, but sometimes it takes longer to copy all items when resizing.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 simple adds + 1 or 2 resizes copying up to 10 items |
| 100 | About 100 simple adds + 2-3 resizes copying up to 100 items |
| 1000 | About 1000 simple adds + 4-5 resizes copying up to 1000 items |
Pattern observation: Most pushes are fast; resizing happens rarely but costs more, so average cost stays low.
Time Complexity: O(1) amortized
This means each push usually takes constant time, but occasionally takes longer when resizing, averaging out to constant time per push.
[X] Wrong: "Every push takes a long time because of copying during resizing."
[OK] Correct: Resizing happens rarely and copying cost spreads over many pushes, so most pushes are quick.
Understanding amortized time helps you explain why dynamic arrays and stacks are efficient in real programs, showing you know how data structures work under the hood.
"What if we increased the array size by only 1 each time instead of doubling? How would the time complexity change?"
Practice
Solution
Step 1: Understand fixed-size array stack limitation
A fixed-size array stack cannot grow beyond its initial capacity, causing overflow if too many elements are pushed.Step 2: Understand dynamic stack behavior
A dynamic stack with a resizable array automatically increases or decreases its size when needed, avoiding overflow and wasting less memory.Final Answer:
It can grow or shrink automatically as elements are pushed or popped. -> Option AQuick Check:
Dynamic resizing = Automatic growth/shrink [OK]
- Thinking dynamic stack has fixed size
- Confusing linked list stack with array stack
- Assuming no resizing is needed
Solution
Step 1: Understand how to resize an array in Go
Go slices cannot be resized directly; we create a new slice with double capacity and copy old elements.Step 2: Check each option
newData := make([]int, len(stack.data)*2); copy(newData, stack.data); stack.data = newData creates a new slice double the size, copies old data, and assigns it back, which is correct resizing.Final Answer:
newData := make([]int, len(stack.data)*2); copy(newData, stack.data); stack.data = newData -> Option BQuick Check:
Resize by new slice + copy = newData := make([]int, len(stack.data)*2); copy(newData, stack.data); stack.data = newData [OK]
- Using append to resize underlying array incorrectly
- Slicing beyond capacity without copying
- Reducing size instead of increasing
func (s *Stack) Push(val int) {
if s.size == len(s.data) {
newData := make([]int, len(s.data)*2)
copy(newData, s.data)
s.data = newData
}
s.data[s.size] = val
s.size++
}
stack := Stack{data: make([]int, 2), size: 0}
stack.Push(10)
stack.Push(20)
stack.Push(30)
fmt.Println(stack.data[:stack.size])
What is the output?Solution
Step 1: Trace initial stack state and pushes
Initial data slice size is 2, size=0. Push 10 at index 0, size=1; Push 20 at index 1, size=2.Step 2: Push 30 triggers resize
Since size == len(data), create newData with size 4, copy old data, assign newData. Then add 30 at index 2, size=3.Step 3: Print slice up to size
Print data[:3] which contains [10 20 30].Final Answer:
[10 20 30] -> Option CQuick Check:
Resize + push = [10 20 30] [OK]
- Forgetting to resize before push
- Printing full data slice including zeros
- Index out of range by pushing without resize
func (s *Stack) Pop() int {
s.size--
val := s.data[s.size]
if s.size < len(s.data)/4 {
newData := make([]int, len(s.data)/2)
copy(newData, s.data[:s.size])
s.data = newData
}
return val
}
Solution
Step 1: Analyze size decrement before access
Decreasing size before accessing data[s.size] can cause s.size to be negative if stack is empty.Step 2: Understand runtime error cause
Accessing data with negative index causes runtime panic (index out of range).Final Answer:
Decreasing size before checking if stack is empty causes negative index. -> Option AQuick Check:
Pop decrements size before check = negative index [OK]
- Not checking if stack is empty before pop
- Resizing array incorrectly
- Accessing data after size decrement without validation
Solution
Step 1: Understand shrinking condition
Shrink when size < 1/4 capacity, but only if new capacity ≥ 4.Step 2: Check each option
if s.size < len(s.data)/4 && len(s.data)/2 >= 4 { newData := make([]int, len(s.data)/2); copy(newData, s.data[:s.size]); s.data = newData } checks size < 1/4 capacity and new capacity ≥ 4, then resizes to half capacity correctly.Final Answer:
if s.size < len(s.data)/4 && len(s.data)/2 >= 4 { newData := make([]int, len(s.data)/2); copy(newData, s.data[:s.size]); s.data = newData } -> Option DQuick Check:
Shrink only if size < 1/4 and new capacity ≥ 4 [OK]
- Shrinking below minimum capacity
- Incorrect condition for shrinking
- Using wrong new capacity size
Ready to prove it?
Pass the test and this pattern moves unknown
