946. Validate Stack Sequences
Problem Description
You are given two integer arrays pushed
and popped
, where each array contains distinct values. These arrays represent a potential sequence of push and pop operations on a stack.
The pushed
array represents the order in which elements were pushed onto a stack, and the popped
array represents the order in which elements were popped from the stack.
Your task is to determine if the popped
sequence is a valid result that could have been obtained by performing push and pop operations on an initially empty stack using the elements from pushed
in the given order.
You need to return true
if it's possible to achieve the popped
sequence through valid stack operations, or false
if it's impossible.
For example:
- If
pushed = [1, 2, 3, 4, 5]
andpopped = [4, 5, 3, 2, 1]
, this is valid because:- Push 1, 2, 3, 4 → Pop 4
- Push 5 → Pop 5
- Pop 3, 2, 1
The key constraint is that you must push elements in the exact order given by pushed
, but you can choose when to pop elements as long as you follow stack rules (last-in-first-out).
Intuition
To validate if a sequence of pops is possible, we need to simulate the actual stack operations. The key insight is that we must push elements in the exact order given by pushed
, but we have flexibility in when we pop elements.
Think about how a stack works: when we push elements, they stack on top of each other. When we pop, we can only remove the top element. So at any point during the pushing process, we can check if the current top of the stack matches the next element we need to pop according to popped
.
The strategy is straightforward:
- Push elements from
pushed
one by one onto a stack - After each push, check if we can perform any pops that match the required order in
popped
- Keep popping as long as the top of the stack matches the next required element in
popped
For example, if we need to pop 4
next (according to popped
), we keep pushing elements until 4
is at the top of the stack. Once it's there, we pop it and move to the next element in popped
.
The process naturally handles all valid sequences because:
- If an element needs to be popped early, it will be popped as soon as it reaches the top
- If an element needs to be popped later, it will wait in the stack until all elements above it are popped first
If we successfully pop all elements in the order specified by popped
, then the sequence is valid. We can verify this by checking if our pointer tracking the position in popped
has reached the end of that array.
Learn more about Stack patterns.
Solution Approach
We implement the solution using stack simulation with a greedy popping strategy. Here's how the algorithm works:
-
Initialize data structures:
- Create an empty stack
stk
to simulate the stack operations - Use a pointer
i = 0
to track the current position in thepopped
array
- Create an empty stack
-
Process each element in
pushed
:- For each element
x
in thepushed
array:- Push
x
onto the stack withstk.append(x)
- After pushing, immediately check if we can perform any valid pops
- Push
- For each element
-
Greedy popping logic:
- While the stack is not empty AND the top element equals
popped[i]
:- Pop the top element from the stack with
stk.pop()
- Increment the pointer
i
to move to the next element inpopped
- Pop the top element from the stack with
- This greedy approach ensures we pop elements as soon as they match the required order
- While the stack is not empty AND the top element equals
-
Validation:
- After processing all elements in
pushed
, check ifi == len(popped)
- If
i
equals the length ofpopped
, it means we successfully popped all elements in the required order, so returntrue
- Otherwise, return
false
- After processing all elements in
The algorithm has a time complexity of O(n)
where n
is the length of the arrays, since each element is pushed and popped at most once. The space complexity is O(n)
for the stack in the worst case when all elements are pushed before any pop occurs.
The key insight is that the greedy popping strategy is both necessary and sufficient - we must pop an element as soon as it appears at the top and matches the next required element in popped
, otherwise we might miss the opportunity to pop it in the correct order later.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with pushed = [1, 2, 3, 4, 5]
and popped = [4, 5, 3, 2, 1]
.
Initial State:
- Stack:
[]
- Pointer
i = 0
(pointing topopped[0] = 4
)
Step 1: Push 1
- Stack:
[1]
- Check if top (1) equals
popped[0]
(4)? No, so don't pop i
remains 0
Step 2: Push 2
- Stack:
[1, 2]
- Check if top (2) equals
popped[0]
(4)? No, so don't pop i
remains 0
Step 3: Push 3
- Stack:
[1, 2, 3]
- Check if top (3) equals
popped[0]
(4)? No, so don't pop i
remains 0
Step 4: Push 4
- Stack:
[1, 2, 3, 4]
- Check if top (4) equals
popped[0]
(4)? Yes! Pop 4 - Stack:
[1, 2, 3]
,i = 1
(now pointing topopped[1] = 5
) - Check if top (3) equals
popped[1]
(5)? No, stop popping
Step 5: Push 5
- Stack:
[1, 2, 3, 5]
- Check if top (5) equals
popped[1]
(5)? Yes! Pop 5 - Stack:
[1, 2, 3]
,i = 2
(now pointing topopped[2] = 3
) - Check if top (3) equals
popped[2]
(3)? Yes! Pop 3 - Stack:
[1, 2]
,i = 3
(now pointing topopped[3] = 2
) - Check if top (2) equals
popped[3]
(2)? Yes! Pop 2 - Stack:
[1]
,i = 4
(now pointing topopped[4] = 1
) - Check if top (1) equals
popped[4]
(1)? Yes! Pop 1 - Stack:
[]
,i = 5
Final Check:
i = 5
which equalslen(popped) = 5
- Return
true
- the sequence is valid!
The key observation is that after pushing 4, we immediately pop it because it matches what we need. Then after pushing 5, we enter a chain of pops (5→3→2→1) as each exposed top element matches the next required element in the popped
sequence.
Solution Implementation
1class Solution:
2 def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
3 """
4 Validates if a given popped sequence could have been generated from the pushed sequence
5 using a stack.
6
7 Args:
8 pushed: List of integers representing the push sequence
9 popped: List of integers representing the pop sequence
10
11 Returns:
12 bool: True if the popped sequence is valid, False otherwise
13 """
14 # Initialize an empty stack to simulate the push/pop operations
15 stack = []
16
17 # Index to track the current position in the popped sequence
18 pop_index = 0
19
20 # Iterate through each element in the pushed sequence
21 for push_value in pushed:
22 # Push the current value onto the stack
23 stack.append(push_value)
24
25 # Try to pop elements that match the expected pop sequence
26 # Continue popping while the stack top matches the current element in popped
27 while stack and stack[-1] == popped[pop_index]:
28 stack.pop()
29 pop_index += 1
30
31 # If all elements in popped have been matched, the sequence is valid
32 return pop_index == len(popped)
33
1class Solution {
2 public boolean validateStackSequences(int[] pushed, int[] popped) {
3 // Use a stack to simulate the push and pop operations
4 Deque<Integer> stack = new ArrayDeque<>();
5
6 // Index to track the current position in the popped array
7 int popIndex = 0;
8
9 // Iterate through each element in the pushed array
10 for (int pushedValue : pushed) {
11 // Push the current element onto the stack
12 stack.push(pushedValue);
13
14 // Try to pop elements from the stack if they match the expected pop sequence
15 // Continue popping while:
16 // 1. The stack is not empty
17 // 2. The top of the stack matches the current element in popped array
18 while (!stack.isEmpty() && stack.peek() == popped[popIndex]) {
19 stack.pop();
20 popIndex++;
21 }
22 }
23
24 // If all elements in popped array have been matched, the sequences are valid
25 return popIndex == popped.length;
26 }
27}
28
1class Solution {
2public:
3 bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
4 // Use a stack to simulate the push and pop operations
5 stack<int> simulationStack;
6
7 // Index to track the current position in the popped sequence
8 int popIndex = 0;
9
10 // Iterate through each element in the pushed sequence
11 for (int currentElement : pushed) {
12 // Push the current element onto the stack
13 simulationStack.push(currentElement);
14
15 // Try to pop elements that match the expected pop sequence
16 // Continue popping while:
17 // 1. The stack is not empty
18 // 2. The top element matches the current expected pop element
19 while (!simulationStack.empty() && simulationStack.top() == popped[popIndex]) {
20 simulationStack.pop();
21 popIndex++;
22 }
23 }
24
25 // If all elements in popped sequence were matched, popIndex should equal popped.size()
26 return popIndex == popped.size();
27 }
28};
29
1/**
2 * Validates if a given popped sequence could be the result of push and pop operations
3 * on an initially empty stack, given the pushed sequence.
4 *
5 * @param pushed - Array representing the order in which elements are pushed to the stack
6 * @param popped - Array representing the order in which elements should be popped from the stack
7 * @returns true if the popped sequence is valid, false otherwise
8 */
9function validateStackSequences(pushed: number[], popped: number[]): boolean {
10 // Initialize an empty stack to simulate push/pop operations
11 const stack: number[] = [];
12
13 // Index to track the current position in the popped array
14 let popIndex = 0;
15
16 // Iterate through each element in the pushed array
17 for (const element of pushed) {
18 // Push the current element onto the stack
19 stack.push(element);
20
21 // Try to pop elements from the stack if they match the expected pop sequence
22 while (stack.length > 0 && stack[stack.length - 1] === popped[popIndex]) {
23 // Remove the top element from the stack
24 stack.pop();
25 // Move to the next element in the popped sequence
26 popIndex++;
27 }
28 }
29
30 // If all elements in popped array were matched, the sequence is valid
31 return popIndex === popped.length;
32}
33
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the pushed
array.
The algorithm iterates through each element in the pushed
array exactly once using the outer for loop. Although there's a while loop inside, each element can only be pushed onto the stack once and popped from the stack once. Therefore, the total number of operations across the entire execution is at most 2n
(each element is pushed once and popped at most once), which simplifies to O(n)
.
Space Complexity: O(n)
, where n
is the length of the pushed
array.
The space complexity is determined by the auxiliary stack stk
used in the algorithm. In the worst-case scenario, all elements from the pushed
array could be on the stack simultaneously before any popping occurs. This happens when the popped
sequence requires all elements to be pushed first before any can be popped (for example, when popped
is the reverse of pushed
). Therefore, the maximum space required is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Check Stack Non-Empty Condition
A critical pitfall is accessing stack[-1]
without first verifying the stack is non-empty. While the provided solution correctly includes stack and
in the while condition, developers often forget this check when implementing the solution from scratch.
Incorrect Implementation:
# WRONG - May cause IndexError while stack[-1] == popped[pop_index]: stack.pop() pop_index += 1
Correct Implementation:
# CORRECT - Checks stack is non-empty first while stack and stack[-1] == popped[pop_index]: stack.pop() pop_index += 1
2. Index Out of Bounds for popped
Array
Another common mistake is not checking if pop_index
is within bounds before accessing popped[pop_index]
. This can cause an IndexError when all elements have been successfully popped.
Incorrect Implementation:
# WRONG - May access popped[pop_index] when pop_index == len(popped) while stack and stack[-1] == popped[pop_index]: stack.pop() pop_index += 1
Correct Implementation:
# CORRECT - Add boundary check for pop_index
while stack and pop_index < len(popped) and stack[-1] == popped[pop_index]:
stack.pop()
pop_index += 1
3. Using Wrong Validation Logic
Some developers mistakenly check if the stack is empty instead of checking if all elements in popped
have been processed.
Incorrect Implementation:
# WRONG - Stack being empty doesn't guarantee all pops were successful
return len(stack) == 0
Correct Implementation:
# CORRECT - Verifies all elements in popped were matched
return pop_index == len(popped)
4. Not Understanding the Greedy Nature is Mandatory
A conceptual pitfall is thinking we have flexibility in when to pop elements. The greedy approach (popping immediately when possible) is not just an optimization—it's mandatory for correctness. Delaying a pop when it's possible will make it impossible to achieve certain valid sequences.
Example demonstrating why greedy is necessary:
pushed = [1, 2]
,popped = [2, 1]
- If we don't pop 2 immediately after pushing it, we'd have stack
[1, 2]
after all pushes - We'd then be forced to pop in order
[2, 1]
, which matches our target - But consider
pushed = [1, 2, 3]
,popped = [2, 3, 1]
- If we don't pop 2 after pushing it, we get stack
[1, 2, 3]
- Now we can't achieve the required pop order since 3 must come before 2
Which of the following is a min heap?
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!