Facebook Pixel

1441. Build an Array With Stack Operations

MediumStackArraySimulation
Leetcode Link

Problem Description

You need to build a specific array using stack operations while reading from a stream of consecutive integers.

You're given:

  • An integer array target that you want to build
  • An integer n representing the upper limit of available numbers

You have access to:

  • A stream containing integers from [1, n] in order (1, 2, 3, ..., n)
  • An initially empty stack
  • Two operations:
    • "Push": adds the next integer from the stream to the top of the stack
    • "Pop": removes the integer from the top of the stack

The goal is to make the stack contents (from bottom to top) exactly match the target array.

Rules to follow:

  1. When you perform a "Push" operation, you must take the next available integer from the stream (you can't skip numbers or go back)
  2. You can "Pop" elements from the stack whenever needed
  3. Once your stack matches target, stop immediately - don't read more numbers or perform more operations
  4. The stream provides numbers sequentially (1, then 2, then 3, etc.)

Example: If target = [1, 3] and n = 3:

  • You need 1 and 3 in your stack, but not 2
  • Since the stream gives you 1, 2, 3 in order, you must:
    • Push 1 (now stack has [1])
    • Push 2 (now stack has [1, 2])
    • Pop 2 (back to [1])
    • Push 3 (now stack has [1, 3])
  • Result: ["Push", "Push", "Pop", "Push"]

The solution simulates this process by tracking which number from the stream we're currently at (cur). For each target number x, if we need to skip some numbers (when cur < x), we push and immediately pop those numbers. When we reach the desired number, we push it and keep it.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is recognizing that we're forced to read numbers from the stream sequentially - we can't skip ahead or go back. This means if we want number 3 in our stack but haven't read 1 and 2 yet, we must read them first.

Think of it like a conveyor belt of numbers moving past you. You must take each number as it comes, but you can immediately throw it away if you don't need it. The "Push" followed by "Pop" combination is our way of "skipping" unwanted numbers.

Let's trace through the thought process:

  1. We need specific numbers from a sequential stream - The target array might be [1, 3, 6], but the stream gives us 1, 2, 3, 4, 5, 6, ... in order.

  2. We can't ignore numbers in the stream - If we want 3, we must first deal with 1 and 2. The only way to "deal with" unwanted numbers is to push them onto the stack and immediately pop them off.

  3. The pattern emerges - For each target number x:

    • All numbers before x that aren't in our target need to be pushed and popped
    • The number x itself needs to be pushed and kept
  4. Why this works - By pushing and immediately popping unwanted numbers, we're essentially discarding them while maintaining our position in the stream. When we reach a number we want, we push it without popping, keeping it in our final stack.

The solution naturally follows: iterate through each target number, skip (push-pop) all numbers between our current position and the target number, then push the target number itself. This ensures our stack builds up exactly the target array from bottom to top.

Learn more about Stack patterns.

Solution Approach

The implementation uses a simulation approach with a simple tracking variable to build the required sequence of operations.

Variables and Data Structures:

  • ans: A list to store the sequence of operations ("Push" and "Pop")
  • cur: An integer tracking the current number from the stream (starts at 1)
  • We iterate through the target array to process each desired number

Algorithm Steps:

  1. Initialize tracking variables

    • Set cur = 1 to represent the first number in the stream
    • Create an empty list ans for storing operations
  2. Process each target number

    For each number x in the target array:

    • Skip unwanted numbers: While cur < x, we need to read and discard numbers from the stream

      while cur < x:
          ans.extend(["Push", "Pop"])  # Push then immediately Pop
          cur += 1                     # Move to next number in stream

      This loop handles all numbers between our current position and the target number we want.

    • Keep the target number: Once cur == x, push the current number and keep it

      ans.append("Push")
      cur += 1

      We only push without popping, keeping this number in our stack.

  3. Return the result

    • After processing all target numbers, ans contains the complete sequence of operations

Example Walkthrough:

For target = [1, 3]:

  • Start with cur = 1, ans = []
  • Process x = 1:
    • cur == 1, so skip the while loop
    • Push 1: ans = ["Push"], cur = 2
  • Process x = 3:
    • cur = 2 < 3, so enter while loop
    • Push and Pop 2: ans = ["Push", "Push", "Pop"], cur = 3
    • Now cur == 3, exit while loop
    • Push 3: ans = ["Push", "Push", "Pop", "Push"], cur = 4
  • Return ["Push", "Push", "Pop", "Push"]

The beauty of this solution is its simplicity - we just track where we are in the stream and push-pop to skip unwanted numbers, only keeping the ones we need.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through target = [2, 4] with n = 5:

Initial State:

  • Stream provides: 1, 2, 3, 4, 5 (in order)
  • Stack: empty
  • cur = 1 (tracking our position in the stream)
  • ans = [] (to store our operations)

Step 1: Build to target[0] = 2

  • We want 2, but cur = 1 < 2
  • Must read 1 first (can't skip in stream)
  • Push 1, then Pop 1 (discard it): ans = ["Push", "Pop"]
  • Move to next: cur = 2
  • Now cur == 2, so Push 2 and keep it: ans = ["Push", "Pop", "Push"]
  • Stack now contains: [2]
  • Move to next: cur = 3

Step 2: Build to target[1] = 4

  • We want 4, but cur = 3 < 4
  • Must read 3 first
  • Push 3, then Pop 3 (discard it): ans = ["Push", "Pop", "Push", "Push", "Pop"]
  • Move to next: cur = 4
  • Now cur == 4, so Push 4 and keep it: ans = ["Push", "Pop", "Push", "Push", "Pop", "Push"]
  • Stack now contains: [2, 4]
  • Move to next: cur = 5

Final Result:

  • Stack: [2, 4] ✓ (matches target)
  • Operations: ["Push", "Pop", "Push", "Push", "Pop", "Push"]

The pattern is clear: we Push-Pop to skip numbers we don't want (1 and 3), and just Push to keep numbers we do want (2 and 4).

Solution Implementation

1class Solution:
2    def buildArray(self, target: List[int], n: int) -> List[str]:
3        """
4        Build an array using a stack machine with Push and Pop operations.
5        The stack machine pushes numbers from 1 to n sequentially.
6      
7        Args:
8            target: Target array to build (sorted in ascending order)
9            n: Maximum number that can be pushed (1 to n)
10      
11        Returns:
12            List of operations ("Push" or "Pop") to build the target array
13        """
14        operations = []
15        current_number = 1
16      
17        # Iterate through each target number
18        for target_number in target:
19            # Push and immediately pop all numbers before the target number
20            # These numbers are not needed in the final array
21            while current_number < target_number:
22                operations.extend(["Push", "Pop"])
23                current_number += 1
24          
25            # Push the target number (it will stay in the stack)
26            operations.append("Push")
27            current_number += 1
28      
29        return operations
30
1class Solution {
2    public List<String> buildArray(int[] target, int n) {
3        // Initialize the result list to store the operations
4        List<String> result = new ArrayList<>();
5      
6        // Start with the first number in the stream (1-indexed)
7        int currentNumber = 1;
8      
9        // Iterate through each target number we need to build
10        for (int targetNumber : target) {
11            // For numbers between current and target, we need to push and immediately pop
12            // since they are not in our target array
13            while (currentNumber < targetNumber) {
14                result.add("Push");
15                result.add("Pop");
16                currentNumber++;
17            }
18          
19            // When we reach the target number, push it onto the stack
20            result.add("Push");
21            currentNumber++;
22        }
23      
24        return result;
25    }
26}
27
1class Solution {
2public:
3    vector<string> buildArray(vector<int>& target, int n) {
4        vector<string> result;
5        int currentNumber = 1;  // Start from 1 as the stream begins with 1
6      
7        // Iterate through each target number we need to build
8        for (int targetNumber : target) {
9            // For numbers between current and target that we don't need,
10            // push them and immediately pop them
11            while (currentNumber < targetNumber) {
12                result.push_back("Push");
13                result.push_back("Pop");
14                currentNumber++;
15            }
16          
17            // Push the target number we actually want to keep
18            result.push_back("Push");
19            currentNumber++;
20        }
21      
22        return result;
23    }
24};
25
1/**
2 * Builds an array of stack operations to create the target array
3 * from a stream of numbers 1 to n
4 * @param target - The target array to build
5 * @param n - The upper limit of the number stream (1 to n)
6 * @returns Array of "Push" and "Pop" operations
7 */
8function buildArray(target: number[], n: number): string[] {
9    // Array to store the sequence of operations
10    const operations: string[] = [];
11  
12    // Current number in the stream (starting from 1)
13    let currentNumber: number = 1;
14  
15    // Iterate through each target number
16    for (const targetNumber of target) {
17        // Push and immediately pop all numbers before the target number
18        // These numbers are not needed in the final array
19        for (; currentNumber < targetNumber; ++currentNumber) {
20            operations.push('Push', 'Pop');
21        }
22      
23        // Push the target number (this one stays in the stack)
24        operations.push('Push');
25      
26        // Move to the next number in the stream
27        ++currentNumber;
28    }
29  
30    return operations;
31}
32

Time and Space Complexity

The time complexity is O(m), where m = target[-1] (the last element in the target array). This is because in the worst case, we need to iterate from 1 to the largest element in the target array. For each number from 1 to target[-1], we either perform a "Push" operation (if it's in target) or a "Push" followed by "Pop" operation (if it's not in target). The while loop runs target[-1] - len(target) times for missing numbers, and we process each element in target once.

The space complexity is O(1) if we exclude the output array ans. We only use a constant amount of extra space with the variable cur to track the current number. If we include the output array, the space complexity would be O(m) where m = target[-1], as we need at most 2 * (target[-1] - 1) operations in the worst case.

Note: The reference answer states the time complexity as O(n) where n is the length of the target array, but this is incorrect. The actual time complexity depends on the value of the last element in target, not just the length of the target array.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Forgetting to Stop at the Target Array's End

A common mistake is continuing to process numbers beyond what's needed. Once you've built the target array, you should stop immediately - don't read more numbers from the stream.

Incorrect approach:

# Wrong: Processing all numbers up to n
for i in range(1, n + 1):
    if i in target:
        operations.append("Push")
    else:
        operations.extend(["Push", "Pop"])

Why it's wrong: If target = [1, 3] and n = 100, this would generate operations for numbers 4 through 100, which is unnecessary and incorrect.

Correct approach: Stop after the last target element is processed (as shown in the solution).

2. Mishandling the Current Number Tracking

Another pitfall is incorrectly managing the current_number variable, especially forgetting to increment it after operations.

Incorrect approach:

for target_number in target:
    while current_number < target_number:
        operations.extend(["Push", "Pop"])
        # Forgot to increment current_number!
    operations.append("Push")
    # Forgot to increment here too!

Why it's wrong: This creates an infinite loop when current_number < target_number because current_number never changes.

Correct approach: Always increment current_number after each Push operation (whether followed by Pop or not).

3. Using Set Membership Instead of Sequential Processing

Some might try to check if each number is in the target using set membership, but this misses the sequential nature of the problem.

Incorrect approach:

target_set = set(target)
for i in range(1, max(target) + 1):
    if i in target_set:
        operations.append("Push")
    else:
        operations.extend(["Push", "Pop"])

Why it's wrong: While this might seem to work, it doesn't leverage the fact that target is already sorted, and it processes numbers unnecessarily if there are gaps at the end.

Correct approach: Process the target array sequentially, which naturally handles the sorted order and stops at the right time.

4. Off-by-One Errors with Initial Value

Starting current_number at 0 instead of 1 is a subtle but critical error.

Incorrect approach:

current_number = 0  # Wrong starting point
for target_number in target:
    while current_number < target_number:
        operations.extend(["Push", "Pop"])
        current_number += 1
    operations.append("Push")
    current_number += 1

Why it's wrong: The stream starts at 1, not 0. This would cause you to push and pop an extra number at the beginning.

Correct approach: Initialize current_number = 1 since the stream provides integers starting from 1.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What is the best way of checking if an element exists in a sorted array once in terms of time complexity? Select the best that applies.


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More