164 lines
8.2 KiB
YAML
164 lines
8.2 KiB
YAML
title: Build an Array With Stack Operations
|
|
slug: build-an-array-with-stack-operations
|
|
difficulty: easy
|
|
leetcode_id: 1441
|
|
leetcode_url: https://leetcode.com/problems/build-an-array-with-stack-operations/
|
|
categories:
|
|
- arrays
|
|
- stack
|
|
patterns:
|
|
- two-pointers
|
|
|
|
description: |
|
|
You are given an integer array `target` and an integer `n`.
|
|
|
|
You have an empty stack with the two following operations:
|
|
|
|
- **`"Push"`**: pushes an integer to the top of the stack.
|
|
- **`"Pop"`**: removes the integer on the top of the stack.
|
|
|
|
You also have a stream of the integers in the range `[1, n]`.
|
|
|
|
Use the two stack operations to make the numbers in the stack (from the bottom to the top) equal to `target`. You should follow the following rules:
|
|
|
|
- If the stream of the integers is not empty, pick the next integer from the stream and push it to the top of the stack.
|
|
- If the stack is not empty, pop the integer at the top of the stack.
|
|
- If, at any moment, the elements in the stack (from the bottom to the top) are equal to `target`, do not read new integers from the stream and do not do more operations on the stack.
|
|
|
|
Return *the stack operations needed to build* `target` *following the mentioned rules*. If there are multiple valid answers, return **any of them**.
|
|
|
|
constraints: |
|
|
- `1 <= target.length <= 100`
|
|
- `1 <= n <= 100`
|
|
- `1 <= target[i] <= n`
|
|
- `target` is strictly increasing
|
|
|
|
examples:
|
|
- input: "target = [1,3], n = 3"
|
|
output: '["Push","Push","Pop","Push"]'
|
|
explanation: "Read 1 from stream and push (s = [1]). Read 2 and push (s = [1,2]). Pop 2 (s = [1]). Read 3 and push (s = [1,3])."
|
|
- input: "target = [1,2,3], n = 3"
|
|
output: '["Push","Push","Push"]'
|
|
explanation: "Read 1, 2, 3 from stream and push each. All target values appear consecutively, so no pops needed."
|
|
- input: "target = [1,2], n = 4"
|
|
output: '["Push","Push"]'
|
|
explanation: "Read 1 and push, read 2 and push. Stack equals target, so stop. Do not read 3 or 4."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you're standing at a conveyor belt that delivers numbers `1, 2, 3, ...` in order. You have a stack beside you, and your goal is to end up with specific numbers in the stack (the `target` array).
|
|
|
|
The key insight is that you **must process numbers in order** from the stream. You cannot skip ahead to grab a number you want — you must push each number as it arrives. If a number isn't in your target, you immediately pop it off.
|
|
|
|
Think of it like sorting mail: every piece of mail comes to your desk in sequence. You keep the ones addressed to you (push and keep) and discard the ones that aren't (push then pop). The "strictly increasing" constraint on `target` means you'll never need to rearrange — you just need to filter.
|
|
|
|
The question becomes: for each number in the stream, is it in the target or not? If it is, keep it. If it's not, push and immediately pop (to simulate "processing" it without keeping it).
|
|
|
|
approach: |
|
|
We solve this by **simulating the stream** and comparing each number against the target:
|
|
|
|
**Step 1: Initialise variables**
|
|
|
|
- `operations`: An empty list to store our "Push" and "Pop" commands
|
|
- `target_idx`: A pointer starting at `0` to track which target element we're looking for next
|
|
|
|
|
|
|
|
**Step 2: Iterate through the stream from 1 to the largest target value**
|
|
|
|
- We only need to iterate up to `target[-1]` (the last element), not `n`, since we stop when the stack matches the target
|
|
- For each number `i` in the stream:
|
|
- Always append `"Push"` (we must push every number from the stream)
|
|
- If `i` equals `target[target_idx]`, it's a number we want to keep — increment `target_idx`
|
|
- If `i` does not equal `target[target_idx]`, it's not wanted — append `"Pop"` to remove it
|
|
|
|
|
|
|
|
**Step 3: Return the operations list**
|
|
|
|
- The list now contains the exact sequence of operations to build the target stack
|
|
|
|
common_pitfalls:
|
|
- title: Iterating Up to n Instead of target[-1]
|
|
description: |
|
|
The problem states you can read integers up to `n`, but you should **stop as soon as the stack matches the target**. If `target = [1, 2]` and `n = 100`, you only need to process numbers 1 and 2.
|
|
|
|
Reading beyond the last target element would add unnecessary operations and could produce incorrect output (the problem explicitly says to stop when the stack equals target).
|
|
wrong_approach: "Iterate from 1 to n"
|
|
correct_approach: "Iterate from 1 to target[-1]"
|
|
|
|
- title: Forgetting to Pop Unwanted Numbers
|
|
description: |
|
|
Every number from the stream must be pushed (you cannot skip numbers). If the number isn't in the target, you must immediately pop it.
|
|
|
|
For `target = [1, 3]`, when you encounter `2`, you cannot ignore it. You must push 2 (stream rules), then pop 2 (to remove it from the stack).
|
|
wrong_approach: "Only push target numbers"
|
|
correct_approach: "Push every number, pop non-target numbers immediately"
|
|
|
|
- title: Using Actual Stack Data Structure
|
|
description: |
|
|
You don't need to maintain an actual stack. Since the target is strictly increasing, you just need to track which target element you're looking for next using a pointer.
|
|
|
|
This keeps the solution simple and avoids unnecessary memory usage.
|
|
wrong_approach: "Maintain a stack and check contents"
|
|
correct_approach: "Use a pointer to track position in target"
|
|
|
|
key_takeaways:
|
|
- "**Simulation pattern**: When a problem describes a process with rules, simulate it step by step"
|
|
- "**Two-pointer technique**: One pointer for the stream (loop variable), one for the target array"
|
|
- "**Strictly increasing constraint**: This guarantees we never need to backtrack or reorder — a hint that a single pass works"
|
|
- "**Optimise the range**: Only iterate as far as needed (`target[-1]`), not the full range (`n`)"
|
|
|
|
time_complexity: "O(k) where k is the maximum value in `target`. We iterate from 1 to `target[-1]`, performing constant work per iteration."
|
|
space_complexity: "O(k) for the output array. The number of operations is at most `2 * target[-1]` (push and optional pop for each number)."
|
|
|
|
solutions:
|
|
- approach_name: Stream Simulation
|
|
is_optimal: true
|
|
code: |
|
|
def build_array(target: list[int], n: int) -> list[str]:
|
|
operations = []
|
|
target_idx = 0 # Points to the next target element we need
|
|
|
|
# Only iterate up to the largest target value
|
|
for i in range(1, target[-1] + 1):
|
|
# Every number from stream must be pushed
|
|
operations.append("Push")
|
|
|
|
if i == target[target_idx]:
|
|
# This number is in our target, keep it
|
|
target_idx += 1
|
|
else:
|
|
# This number isn't wanted, pop it immediately
|
|
operations.append("Pop")
|
|
|
|
return operations
|
|
explanation: |
|
|
**Time Complexity:** O(k) where k = `target[-1]` — we iterate from 1 to the largest target value.
|
|
|
|
**Space Complexity:** O(k) — the output list contains at most 2k operations.
|
|
|
|
We simulate reading from the stream. Each number gets pushed. If it matches our current target, we keep it and move to the next target. If not, we pop it immediately. The strictly increasing nature of target ensures we never need to revisit decisions.
|
|
|
|
- approach_name: Set-Based Lookup
|
|
is_optimal: false
|
|
code: |
|
|
def build_array(target: list[int], n: int) -> list[str]:
|
|
operations = []
|
|
target_set = set(target) # For O(1) lookup
|
|
max_target = target[-1]
|
|
|
|
for i in range(1, max_target + 1):
|
|
operations.append("Push")
|
|
if i not in target_set:
|
|
# Number not needed, remove it
|
|
operations.append("Pop")
|
|
|
|
return operations
|
|
explanation: |
|
|
**Time Complexity:** O(k) where k = `target[-1]` — same iteration range.
|
|
|
|
**Space Complexity:** O(m + k) where m is the length of target (for the set) and k for the output.
|
|
|
|
This approach uses a set for O(1) membership testing instead of a pointer. While equally correct, it uses extra space for the set. The pointer approach is more elegant since the strictly increasing property makes sequential comparison sufficient.
|