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 ofmaxSizeelements. The stack cannot hold more thanmaxSizeelements. -
Push operation
push(int x): Adds elementxto 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 bottomkelements in the stack byval. If the stack contains fewer thankelements, 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
isince 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 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 711class 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 */ 781class 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 */ 741// 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} 74Solution 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 sizemaxSizeto store the actual stack elementsadd: An array of sizemaxSizeto 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
stkarray withmaxSizecapacity, initialized with zeros - Create
addarray withmaxSizecapacity, initialized with zeros - Set
i = 0to indicate the stack is empty
- Create
-
Push Operation (
push):- Check if there's space:
if self.i < len(self.stk) - If yes, place element
xat positionstk[i] - Increment
iby 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
iby 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), addvaltoadd[i] - This marks that all elements from bottom to position
ishould 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 5-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.
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) - 1and updates a single element in theself.addarray. 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.stkandself.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
Which type of traversal does breadth first search do?
Recommended Readings
Stack Intro New to stacks This article provides a quick review For a comprehensive beginner friendly lesson on stacks check out our Foundation Course Stack module courses foundation stack_lifo_model Imagine you have a pile of books on your desk If you want to add a new book you place it on top
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 If you prefer videos here's a video that explains recursion in a fun and easy way 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 him
Want a Structured Path to Master System Design Too? Don’t Miss This!