1381. Design a Stack With Increment Operation
Problem Description
You need to design a custom stack data structure that supports standard stack operations plus an increment operation.
The CustomStack
class should support:
-
Constructor
CustomStack(int maxSize)
: Creates a stack with a maximum capacity ofmaxSize
elements. The stack cannot hold more thanmaxSize
elements. -
Push operation
push(int x)
: Adds elementx
to the top of the stack. If the stack has already reached its maximum capacity (maxSize
), the element should not be added. -
Pop operation
pop()
: Removes and returns the element from the top of the stack. If the stack is empty, returns-1
. -
Increment operation
increment(int k, int val)
: Increases the value of the bottomk
elements in the stack byval
. If the stack contains fewer thank
elements, all elements in the stack should be incremented byval
.
For example, if the stack contains [1, 2, 3]
(with 3 at the top) and you call increment(2, 100)
, the stack becomes [101, 102, 3]
because the bottom 2 elements are incremented by 100.
The solution uses an optimization technique with a lazy propagation approach. Instead of directly modifying all elements during the increment operation, it uses an additional array add
to track cumulative increments at each position. This allows the increment operation to run in O(1) time. When popping an element, the accumulated increment value is applied and propagated to the element below it if necessary.
Intuition
The straightforward approach would be to use a simple array to simulate the stack and directly modify elements during the increment operation. However, this would make the increment
operation O(k) in time complexity, as we'd need to iterate through and update k elements.
The key insight is that we can defer the actual increments until we need to access the elements. Think about it this way: if we increment the bottom k elements by some value, and then increment them again before popping, we're doing redundant work by updating the same elements multiple times.
Instead of immediately applying the increment to all k elements, we can just "mark" that an increment needs to be applied. We use a separate array add
where add[i]
stores the cumulative increment that should be applied to the element at position i
when it's accessed.
The clever part comes with how we handle these increments during the pop
operation. When we pop an element at position i
, we:
- Apply the increment stored at
add[i]
to get the actual value - Pass this increment down to the element below it (
add[i-1] += add[i]
) - Clear the increment at position
i
since we've already used it
This "passing down" mechanism ensures that any increments meant for elements below the popped element are preserved. For example, if we increment the bottom 3 elements and then pop the 3rd element, the remaining 2 elements still need to remember they should be incremented.
By using this lazy propagation technique, we achieve O(1) time complexity for all operations: push
and pop
work with single elements, and increment
only updates a single position in the add
array rather than iterating through k elements.
Learn more about Stack patterns.
Solution Approach
The implementation uses two arrays and an index pointer to efficiently manage the custom stack with lazy increment propagation.
Data Structures:
stk
: An array of sizemaxSize
to store the actual stack elementsadd
: An array of sizemaxSize
to store cumulative increment values at each positioni
: An integer pointer indicating the next position to insert an element (also represents the current size of the stack)
Implementation Details:
-
Initialization (
__init__
):- Create
stk
array withmaxSize
capacity, initialized with zeros - Create
add
array withmaxSize
capacity, initialized with zeros - Set
i = 0
to indicate the stack is empty
- Create
-
Push Operation (
push
):- Check if there's space:
if self.i < len(self.stk)
- If yes, place element
x
at positionstk[i]
- Increment
i
by 1 to update the stack size - Time complexity: O(1)
- Check if there's space:
-
Pop Operation (
pop
):- Check if stack is empty:
if self.i <= 0
, return-1
- Decrement
i
by 1 to point to the top element - Calculate the actual value:
ans = stk[i] + add[i]
- Propagate the increment downward: if there's an element below (
i > 0
), add the current increment to it:add[i-1] += add[i]
- Clear the current position's increment:
add[i] = 0
- Return the calculated answer
- Time complexity: O(1)
- Check if stack is empty:
-
Increment Operation (
increment
):- Calculate the actual position to increment:
i = min(k, self.i) - 1
- This ensures we don't exceed the current stack size
- If
i >= 0
(stack is not empty), addval
toadd[i]
- This marks that all elements from bottom to position
i
should be incremented byval
- Time complexity: O(1)
- Calculate the actual position to increment:
Key Pattern - Lazy Propagation: The solution employs lazy propagation to defer the actual increment operations. Instead of immediately updating all k elements, we only mark the topmost affected position with the increment value. When elements are popped, the increment values cascade down to the remaining elements, ensuring correctness while maintaining O(1) complexity for all operations.
For example, if we have stack [1, 2, 3]
and call increment(2, 100)
, instead of immediately updating to [101, 102, 3]
, we just set add[1] = 100
. When we pop elements, the actual values are computed on-the-fly by adding the base value and the accumulated increment.
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 sequence of operations on a CustomStack with maxSize = 3:
Initial State:
stk = [0, 0, 0]
add = [0, 0, 0]
i = 0
(stack is empty)
Operation 1: push(5)
- Place 5 at position 0:
stk = [5, 0, 0]
- Increment i:
i = 1
- State:
stk = [5, 0, 0]
,add = [0, 0, 0]
Operation 2: push(7)
- Place 7 at position 1:
stk = [5, 7, 0]
- Increment i:
i = 2
- State:
stk = [5, 7, 0]
,add = [0, 0, 0]
Operation 3: push(10)
- Place 10 at position 2:
stk = [5, 7, 10]
- Increment i:
i = 3
- State:
stk = [5, 7, 10]
,add = [0, 0, 0]
Operation 4: increment(2, 100)
- Want to increment bottom 2 elements
- Calculate position:
min(2, 3) - 1 = 1
- Set
add[1] = 100
(marking that bottom 2 elements need +100) - State:
stk = [5, 7, 10]
,add = [0, 100, 0]
- Note: Actual values are [5, 107, 10] but we haven't modified stk yet
Operation 5: pop()
- Decrement i:
i = 2
(pointing to top element) - Calculate value:
stk[2] + add[2] = 10 + 0 = 10
- No increment to propagate since
add[2] = 0
- Clear:
add[2] = 0
(already 0) - Return: 10
- State:
stk = [5, 7, 10]
,add = [0, 100, 0]
,i = 2
Operation 6: pop()
- Decrement i:
i = 1
- Calculate value:
stk[1] + add[1] = 7 + 100 = 107
- Propagate increment down:
add[0] = add[0] + add[1] = 0 + 100 = 100
- Clear:
add[1] = 0
- Return: 107
- State:
stk = [5, 7, 10]
,add = [100, 0, 0]
,i = 1
Operation 7: increment(1, 50)
- Want to increment bottom 1 element
- Calculate position:
min(1, 1) - 1 = 0
- Add to existing increment:
add[0] = 100 + 50 = 150
- State:
stk = [5, 7, 10]
,add = [150, 0, 0]
,i = 1
Operation 8: pop()
- Decrement i:
i = 0
- Calculate value:
stk[0] + add[0] = 5 + 150 = 155
- No element below to propagate to (i = 0)
- Clear:
add[0] = 0
- Return: 155
- State:
stk = [5, 7, 10]
,add = [0, 0, 0]
,i = 0
(empty stack)
This example demonstrates how the lazy propagation works: increments are stored in the add
array and only applied when elements are popped, with the increment values cascading down to lower elements as needed.
Solution Implementation
1class CustomStack:
2 def __init__(self, maxSize: int):
3 """
4 Initialize the custom stack with a maximum size.
5
6 Args:
7 maxSize: Maximum number of elements the stack can hold
8 """
9 # Pre-allocate arrays for stack elements and increment values
10 self.stack = [0] * maxSize
11 # Lazy propagation array to store pending increments
12 self.increment_values = [0] * maxSize
13 # Current size of the stack (points to next empty position)
14 self.current_size = 0
15
16 def push(self, x: int) -> None:
17 """
18 Push an element onto the stack if not full.
19
20 Args:
21 x: Element to push onto the stack
22 """
23 # Only push if stack is not full
24 if self.current_size < len(self.stack):
25 self.stack[self.current_size] = x
26 self.current_size += 1
27
28 def pop(self) -> int:
29 """
30 Pop and return the top element from the stack.
31
32 Returns:
33 The top element value (with accumulated increments), or -1 if stack is empty
34 """
35 # Return -1 if stack is empty
36 if self.current_size <= 0:
37 return -1
38
39 # Move pointer to the top element
40 self.current_size -= 1
41
42 # Calculate the actual value including any pending increments
43 result = self.stack[self.current_size] + self.increment_values[self.current_size]
44
45 # Propagate the increment value to the element below (if exists)
46 if self.current_size > 0:
47 self.increment_values[self.current_size - 1] += self.increment_values[self.current_size]
48
49 # Clear the increment value for this position
50 self.increment_values[self.current_size] = 0
51
52 return result
53
54 def increment(self, k: int, val: int) -> None:
55 """
56 Increment the bottom k elements of the stack by val.
57 Uses lazy propagation for O(1) time complexity.
58
59 Args:
60 k: Number of bottom elements to increment
61 val: Value to add to each element
62 """
63 # Find the index of the k-th element (or top if stack has fewer than k elements)
64 target_index = min(k, self.current_size) - 1
65
66 # Apply increment only if there are elements to increment
67 if target_index >= 0:
68 # Add to the increment array at the topmost affected position
69 # This value will propagate down during pop operations
70 self.increment_values[target_index] += val
71
1class CustomStack {
2 private int[] stack; // Array to store stack elements
3 private int[] incrementValues; // Array to store lazy increment values for each position
4 private int top; // Current top index of the stack (also represents size)
5
6 /**
7 * Initializes the custom stack with a maximum capacity.
8 * @param maxSize The maximum number of elements the stack can hold
9 */
10 public CustomStack(int maxSize) {
11 stack = new int[maxSize];
12 incrementValues = new int[maxSize];
13 top = 0;
14 }
15
16 /**
17 * Pushes an element onto the stack if it's not full.
18 * @param x The element to be pushed
19 */
20 public void push(int x) {
21 if (top < stack.length) {
22 stack[top] = x;
23 top++;
24 }
25 }
26
27 /**
28 * Pops and returns the top element from the stack.
29 * Returns -1 if the stack is empty.
30 * @return The popped element or -1 if stack is empty
31 */
32 public int pop() {
33 // Check if stack is empty
34 if (top <= 0) {
35 return -1;
36 }
37
38 // Decrement top to point to the actual top element
39 top--;
40
41 // Calculate the actual value including any pending increments
42 int result = stack[top] + incrementValues[top];
43
44 // Propagate the increment value to the element below (if exists)
45 if (top > 0) {
46 incrementValues[top - 1] += incrementValues[top];
47 }
48
49 // Clear the increment value for the popped position
50 incrementValues[top] = 0;
51
52 return result;
53 }
54
55 /**
56 * Increments the bottom k elements of the stack by val.
57 * If there are fewer than k elements, increments all elements.
58 * @param k The number of bottom elements to increment
59 * @param val The value to increment by
60 */
61 public void increment(int k, int val) {
62 if (top > 0) {
63 // Apply increment to the minimum of k and current stack size
64 // Index is (min - 1) because we're using 0-based indexing
65 int targetIndex = Math.min(top, k) - 1;
66 incrementValues[targetIndex] += val;
67 }
68 }
69}
70
71/**
72 * Your CustomStack object will be instantiated and called as such:
73 * CustomStack obj = new CustomStack(maxSize);
74 * obj.push(x);
75 * int param_2 = obj.pop();
76 * obj.increment(k,val);
77 */
78
1class CustomStack {
2public:
3 /**
4 * Constructor: Initialize a custom stack with a maximum size
5 * @param maxSize: The maximum capacity of the stack
6 */
7 CustomStack(int maxSize) {
8 stack.resize(maxSize);
9 incrementValues.resize(maxSize);
10 topIndex = 0;
11 }
12
13 /**
14 * Push an element onto the stack if not full
15 * @param x: The value to push onto the stack
16 */
17 void push(int x) {
18 if (topIndex < stack.size()) {
19 stack[topIndex++] = x;
20 }
21 }
22
23 /**
24 * Pop and return the top element from the stack
25 * @return: The top element value (with accumulated increments), or -1 if stack is empty
26 */
27 int pop() {
28 // Check if stack is empty
29 if (topIndex <= 0) {
30 return -1;
31 }
32
33 // Decrement top index and calculate the actual value with increments
34 int result = stack[--topIndex] + incrementValues[topIndex];
35
36 // Propagate the increment value to the element below (if exists)
37 if (topIndex > 0) {
38 incrementValues[topIndex - 1] += incrementValues[topIndex];
39 }
40
41 // Reset the increment value for the popped position
42 incrementValues[topIndex] = 0;
43
44 return result;
45 }
46
47 /**
48 * Increment the bottom k elements of the stack by val
49 * @param k: Number of bottom elements to increment
50 * @param val: The value to add to each of the bottom k elements
51 */
52 void increment(int k, int val) {
53 // Only increment if stack is not empty
54 if (topIndex > 0) {
55 // Apply increment to the min(k, topIndex)-th element
56 // This uses lazy propagation - the increment is stored and applied during pop
57 incrementValues[min(k, topIndex) - 1] += val;
58 }
59 }
60
61private:
62 vector<int> stack; // Array to store stack elements
63 vector<int> incrementValues; // Array to store lazy increment values for each position
64 int topIndex; // Current top index of the stack (number of elements)
65};
66
67/**
68 * Your CustomStack object will be instantiated and called as such:
69 * CustomStack* obj = new CustomStack(maxSize);
70 * obj->push(x);
71 * int param_2 = obj->pop();
72 * obj->increment(k,val);
73 */
74
1// Stack array to store elements
2let stack: number[] = [];
3// Array to store lazy increment values for each position
4let incrementArray: number[] = [];
5// Current index pointing to the next available position in stack
6let currentIndex: number = 0;
7// Maximum size of the stack
8let maxStackSize: number = 0;
9
10/**
11 * Initialize the custom stack with a maximum size
12 * @param maxSize - Maximum number of elements the stack can hold
13 */
14function initializeStack(maxSize: number): void {
15 maxStackSize = maxSize;
16 stack = new Array(maxSize).fill(0);
17 incrementArray = new Array(maxSize).fill(0);
18 currentIndex = 0;
19}
20
21/**
22 * Push an element onto the stack if not full
23 * @param x - The value to push onto the stack
24 */
25function push(x: number): void {
26 // Only push if stack hasn't reached maximum capacity
27 if (currentIndex < maxStackSize) {
28 stack[currentIndex] = x;
29 currentIndex++;
30 }
31}
32
33/**
34 * Pop and return the top element from the stack
35 * @returns The top element value (with accumulated increments) or -1 if stack is empty
36 */
37function pop(): number {
38 // Return -1 if stack is empty
39 if (currentIndex <= 0) {
40 return -1;
41 }
42
43 // Decrement index to point to the top element
44 currentIndex--;
45
46 // Calculate the final value by adding the stored increment value
47 const result = stack[currentIndex] + incrementArray[currentIndex];
48
49 // Propagate the increment value to the element below (if exists)
50 if (currentIndex > 0) {
51 incrementArray[currentIndex - 1] += incrementArray[currentIndex];
52 }
53
54 // Reset the increment value for this position
55 incrementArray[currentIndex] = 0;
56
57 return result;
58}
59
60/**
61 * Increment the bottom k elements of the stack by val
62 * @param k - Number of bottom elements to increment
63 * @param val - Value to add to each of the bottom k elements
64 */
65function increment(k: number, val: number): void {
66 // Only increment if stack is not empty
67 if (currentIndex > 0) {
68 // Apply increment to the minimum of k and current stack size
69 // Using lazy propagation: store increment at the topmost affected position
70 const targetIndex = Math.min(currentIndex, k) - 1;
71 incrementArray[targetIndex] += val;
72 }
73}
74
Time and Space Complexity
Time Complexity: O(1)
for all operations (push
, pop
, and increment
)
push
: Simply assigns a value toself.stk[self.i]
and increments the index, which takes constant time.pop
: Performs a fixed number of operations - decrements the index, calculates the result by addingself.stk[self.i] + self.add[self.i]
, potentially updatesself.add[self.i - 1]
, and resetsself.add[self.i]
. All these operations take constant time.increment
: Calculatesmin(k, self.i) - 1
and updates a single element in theself.add
array. This is a constant time operation regardless of the value ofk
.
The key insight is that the increment
operation uses lazy propagation. Instead of updating all k
elements immediately, it only marks the increment at position i
in the self.add
array. The actual increment is applied when elements are popped, and the increment value is propagated down the stack one level at a time during each pop
operation.
Space Complexity: O(n)
where n
is the maxSize
parameter
- The implementation uses two arrays:
self.stk
andself.add
, each of sizemaxSize
. - The space usage is
2 * maxSize = O(maxSize) = O(n)
. - Additionally, there's a constant amount of space for the index variable
self.i
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Propagate Increments During Pop
One of the most common mistakes is forgetting to propagate the increment value to the element below when popping. This breaks the lazy propagation mechanism.
Incorrect Implementation:
def pop(self) -> int:
if self.current_size <= 0:
return -1
self.current_size -= 1
result = self.stack[self.current_size] + self.increment_values[self.current_size]
# MISTAKE: Forgetting to propagate increment to element below
self.increment_values[self.current_size] = 0 # Only clearing without propagation
return result
Correct Implementation:
def pop(self) -> int:
if self.current_size <= 0:
return -1
self.current_size -= 1
result = self.stack[self.current_size] + self.increment_values[self.current_size]
# Propagate increment to the element below before clearing
if self.current_size > 0:
self.increment_values[self.current_size - 1] += self.increment_values[self.current_size]
self.increment_values[self.current_size] = 0
return result
2. Off-by-One Error in Increment Operation
Another frequent mistake is incorrectly calculating the index for the increment operation, especially when dealing with 1-based k versus 0-based indexing.
Incorrect Implementation:
def increment(self, k: int, val: int) -> None:
# MISTAKE: Using k directly as index without adjustment
target_index = min(k, self.current_size)
if target_index >= 0:
self.increment_values[target_index] += val # This would increment k+1 elements!
Correct Implementation:
def increment(self, k: int, val: int) -> None:
# Correctly subtract 1 to convert from count to index
target_index = min(k, self.current_size) - 1
if target_index >= 0:
self.increment_values[target_index] += val
3. Not Clearing Increment Values After Pop
Failing to reset the increment value at a popped position can cause incorrect values if that position is reused later.
Incorrect Implementation:
def pop(self) -> int:
if self.current_size <= 0:
return -1
self.current_size -= 1
result = self.stack[self.current_size] + self.increment_values[self.current_size]
if self.current_size > 0:
self.increment_values[self.current_size - 1] += self.increment_values[self.current_size]
# MISTAKE: Not clearing the increment value
# self.increment_values[self.current_size] = 0 # Missing this line!
return result
This would cause issues when a new element is pushed to the same position later, as it would inherit the old increment value.
4. Incorrect Boundary Check in Increment
Not properly handling the case when the stack is empty or when k is 0.
Incorrect Implementation:
def increment(self, k: int, val: int) -> None:
# MISTAKE: Not checking if the result is negative
target_index = min(k, self.current_size) - 1
self.increment_values[target_index] += val # Could access index -1!
Correct Implementation:
def increment(self, k: int, val: int) -> None:
target_index = min(k, self.current_size) - 1
# Proper boundary check
if target_index >= 0:
self.increment_values[target_index] += val
Prevention Tips:
- Always trace through the lazy propagation logic with small examples
- Verify that increment values are properly propagated and cleared
- Test edge cases: empty stack, single element, k larger than stack size
- Use consistent naming and clear comments to track the propagation flow
- Remember that the increment array represents cumulative increments that apply to all elements from bottom up to that index
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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!