Design a Stack With Increment Operation
MedPrompt
Design a stack that supports increment operations on its elements.
Implement the CustomStack class:
CustomStack(int maxSize)Initializes the object withmaxSizewhich is the maximum number of elements in the stack.void push(int x)Addsxto the top of the stack if the stack has not reached themaxSize.int pop()Pops and returns the top of the stack or-1if the stack is empty.void inc(int k, int val)Increments the bottomkelements of the stack byval. If there are less thankelements in the stack, increment all the elements in the stack.
Example 1:
Input
["CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"]
[[3],[1],[2],[],[2],[3],[4],[5,100],[2,100],[],[],[],[]]
Output
[null,null,null,2,null,null,null,null,null,103,202,201,-1]
Explanation
CustomStack stk = new CustomStack(3); // Stack is Empty []
stk.push(1); // stack becomes [1]
stk.push(2); // stack becomes [1, 2]
stk.pop(); // return 2 --> Return top of the stack 2, stack becomes [1]
stk.push(2); // stack becomes [1, 2]
stk.push(3); // stack becomes [1, 2, 3]
stk.push(4); // stack still [1, 2, 3], Do not add another elements as size is 4
stk.increment(5, 100); // stack becomes [101, 102, 103]
stk.increment(2, 100); // stack becomes [201, 202, 103]
stk.pop(); // return 103 --> Return top of the stack 103, stack becomes [201, 202]
stk.pop(); // return 202 --> Return top of the stack 202, stack becomes [201]
stk.pop(); // return 201 --> Return top of the stack 201, stack becomes []
stk.pop(); // return -1 --> Stack is empty return -1.
Constraints:
1 <= maxSize, x, k <= 10000 <= val <= 100- At most
1000calls will be made to each method ofincrement,pushandpopeach separately.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses a simple array or list to simulate the stack. The push and pop operations work as they would in a standard array-based stack implementation. The increment operation is handled by directly iterating through the bottom k elements of the array and adding the value val to each one.
Algorithm
- Initialize an integer array
stackof sizemaxSizeto store the elements. - Maintain an integer
topto point to the index of the top element, initialized to -1. push(x): If the stack is not full (top < maxSize - 1), incrementtopand placexatstack[top].pop(): If the stack is not empty (top >= 0), return the element atstack[top]and then decrementtop. Otherwise, return -1.inc(k, val): Iterate from indexi = 0up tomin(k - 1, top). In each iteration, addvaltostack[i]. This directly modifies the values of the bottom elements.
Walkthrough
In this method, we use a standard array to represent the stack's data. A pointer, top, keeps track of the current size and the position for the next push.
- The
pushandpopmethods are implemented in the standard O(1) time complexity for an array-based stack. - The
increment(k, val)method is the most straightforward part. It loops through the firstkelements of the array (or fewer if the stack size is less thank) and addsvalto each. This makes theincrementoperation's performance dependent on the value ofk.
class CustomStack { private int[] stack; private int top; private int maxSize; public CustomStack(int maxSize) { this.maxSize = maxSize; this.stack = new int[maxSize]; this.top = -1; } public void push(int x) { if (top < maxSize - 1) { top++; stack[top] = x; } } public int pop() { if (top == -1) { return -1; } int val = stack[top]; top--; return val; } public void increment(int k, int val) { int limit = Math.min(k, top + 1); for (int i = 0; i < limit; i++) { stack[i] += val; } }}Complexity
Time
- **`push()`**: O(1) - Constant time array access. - **`pop()`**: O(1) - Constant time array access. - **`increment()`**: O(k) - In the worst case, we iterate through `k` elements, which can be up to `maxSize`. So, it's O(maxSize).
Space
O(maxSize) - We need an array of size `maxSize` to store the stack elements.
Trade-offs
Pros
Simple to understand and implement.
Uses a standard array, which is memory-efficient.
Cons
The
incrementoperation is inefficient, with a time complexity linear tok. This can be slow ifkis large andincrementis called frequently.
This approach avoids the costly iteration in the increment method by deferring the additions. It uses an auxiliary array to store the increments, making the increment operation O(1). The actual value of an element is calculated only when it is popped from the stack.
Algorithm
- Initialize a value array
stack, an auxiliary increment arrayincof the same size, and atoppointer. push(x): If not full, incrementtop, setstack[top] = x, and ensureinc[top]is 0.increment(k, val): If the stack is not empty, find the index of the top-most element to be incremented,idx = min(k-1, top). Addvaltoinc[idx]. This is an O(1) step.pop(): If not empty, get the total increment for the top element:increment_value = inc[top]. Calculate the final value:result = stack[top] + increment_value. Before popping, pass the increment down to the element below:inc[top-1] += increment_value(iftop > 0). Resetinc[top] = 0, decrementtop, and returnresult.
Walkthrough
To optimize the increment operation, we can avoid iterating through the elements. The core idea is to use a lazy update mechanism. We maintain a second array, let's call it inc, of the same size as the stack.
inc[i]will store the total increment value that needs to be applied tostack[i]and all elements below it.- When
increment(k, val)is called, instead of addingvaltokelements, we simply addvaltoinc[k-1](or the top-most valid index). This marks that the element at this position and everything below it has an additionalvalto be added. - When
pop()is called for the element attop, we calculate its true value by addingstack[top] + inc[top]. Then, we propagate the increment downwards by addinginc[top]toinc[top-1]. This ensures the element below will correctly account for the increment when it is popped. This makes all operations O(1).
class CustomStack { private int[] stack; private int[] inc; // Stores lazy increments private int top; private int maxSize; public CustomStack(int maxSize) { this.maxSize = maxSize; this.stack = new int[maxSize]; this.inc = new int[maxSize]; this.top = -1; } public void push(int x) { if (top < maxSize - 1) { top++; stack[top] = x; inc[top] = 0; // Reset increment for new element } } public int pop() { if (top == -1) { return -1; } int incrementValue = inc[top]; int result = stack[top] + incrementValue; // Pass the increment down to the element below if (top > 0) { inc[top - 1] += incrementValue; } top--; return result; } public void increment(int k, int val) { if (top == -1) { return; } // The increment applies to elements from index 0 to min(k, size)-1. // We only need to add the increment to the top-most affected element's slot. int limitIndex = Math.min(k - 1, top); inc[limitIndex] += val; }}Complexity
Time
- **`push()`**: O(1) - Constant time. - **`pop()`**: O(1) - Constant time. - **`increment()`**: O(1) - Constant time. All operations are now highly efficient.
Space
O(maxSize) - We use two arrays, `stack` and `inc`, both of size `maxSize`. The total space is `O(2 * maxSize)`, which simplifies to O(maxSize).
Trade-offs
Pros
Extremely efficient, with all operations taking constant time.
Scales well regardless of the number of calls or the value of
k.
Cons
Requires extra space for the increment array, doubling the storage requirement compared to the naive approach.
The logic is slightly more complex to understand due to the deferred nature of the increment operation.
Solutions
Solution
class CustomStack { private int [] stk ; private int [] add ; private int i ; public CustomStack ( int maxSize ) { stk = new int [ maxSize ]; add = new int [ maxSize ]; } public void push ( int x ) { if ( i < stk . length ) { stk [ i ++] = x ; } } public int pop () { if ( i <= 0 ) { return - 1 ; } int ans = stk [-- i ] + add [ i ]; if ( i > 0 ) { add [ i - 1 ] += add [ i ]; } add [ i ] = 0 ; return ans ; } public void increment ( int k , int val ) { if ( i > 0 ) { add [ Math . min ( i , k ) - 1 ] += val ; } } } /** * Your CustomStack object will be instantiated and called as such: * CustomStack obj = new CustomStack(maxSize); * obj.push(x); * int param_2 = obj.pop(); * obj.increment(k,val); */class CustomStack { public: CustomStack ( int maxSize ) { stk . resize ( maxSize ); add . resize ( maxSize ); i = 0 ; } void push ( int x ) { if ( i < stk . size ()) { stk [ i ++ ] = x ; } } int pop () { if ( i <= 0 ) { return - 1 ; } int ans = stk [ -- i ] + add [ i ]; if ( i > 0 ) { add [ i - 1 ] += add [ i ]; } add [ i ] = 0 ; return ans ; } void increment ( int k , int val ) { if ( i > 0 ) { add [ min ( k , i ) - 1 ] += val ; } } private: vector < int > stk ; vector < int > add ; int i ; }; /** * Your CustomStack object will be instantiated and called as such: * CustomStack* obj = new CustomStack(maxSize); * obj->push(x); * int param_2 = obj->pop(); * obj->increment(k,val); */class CustomStack : def __init__ ( self , maxSize : int ): self . stack = [] self . max_size = maxSize self . inc = [] def push ( self , x : int ) -> None : if len ( self . stack ) < self . max_size : self . stack . append ( x ) self . inc . append ( 0 ) # 0 because no range operation # not append(inc[-1]), since it will be updated on pop() def pop ( self ) -> int : if not self . stack : return - 1 if len ( self . inc ) > 1 : self . inc [ - 2 ] += self . inc [ - 1 ] return self . inc . pop () + self . stack . pop () def increment ( self , k : int , val : int ) -> None : if self . inc : self . inc [ min ( k , len ( self . inc )) - 1 ] += val # Your CustomStack object will be instantiated and called as such: # obj = CustomStack(maxSize) # obj.push(x) # param_2 = obj.pop() # obj.increment(k,val) ############### class CustomStack : def __init__ ( self , maxSize : int ): self . stk = [ 0 ] * maxSize self . add = [ 0 ] * maxSize self . i = 0 def push ( self , x : int ) -> None : if self . i < len ( self . stk ): self . stk [ self . i ] = x self . i += 1 def pop ( self ) -> int : if self . i <= 0 : return - 1 self . i -= 1 ans = self . stk [ self . i ] + self . add [ self . i ] if self . i > 0 : self . add [ self . i - 1 ] += self . add [ self . i ] self . add [ self . i ] = 0 return ans def increment ( self , k : int , val : int ) -> None : i = min ( k , self . i ) - 1 if i >= 0 : self . add [ i ] += val # Your CustomStack object will be instantiated and called as such: # obj = CustomStack(maxSize) # obj.push(x) # param_2 = obj.pop() # obj.increment(k,val)Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.