Design a SpecialStack that supports push(x), pop(), peek(), isEmpty(), and getMax() in O(1) time.
- push(x) → add element x
- pop() → remove and return top element; return -1 if empty
- peek() → return top element without removing; -1 if empty
- isEmpty() → return true if stack is empty, else false
- getMax() → return maximum element; -1 if empty
All operations must run in O(1).
Examples:
Input: operations[] = [push(2), push(3), peek(), pop(), getMax(), push(1), getMax()]
Output: [3, 2, 2]
Explanation:
push(2): Stack is [2]
push(3): Stack is [2, 3]
peek(): Top element is 3
pop(): Removes top element 3, stack is [2]
getMax(): Maximum element is 2
push(1): Stack is [2, 1]
getMax(): Maximum element is 2
Table of Content
[Approach 1] Using an Auxiliary Stack - O(1) Time and O(n) Space
The idea is to use two stacks: a main stack to store the actual elements and an auxiliary stack to track the maximum elements. The auxiliary stack always has its top as the current maximum.
How to Maintain Maximum Element?
Whenever we push an element into the main stack, we also update the auxiliary stack:
- If the auxiliary stack is empty, or the new element is greater than or equal to the current maximum (top of the auxiliary stack), then we push this new element into the auxiliary stack as well.
- Otherwise, we push the same maximum value (the top of the auxiliary stack) again, so that both stacks remain the same size, and the auxiliary stack always has the current maximum at its top.
Whenever we pop an element from the main stack, we also pop from the auxiliary stack. This way, both stacks always stay in sync. Thus, at any time, the top of the auxiliary stack represents the maximum element of the main stack, and getMax() can be returned in constant time.
#include <iostream>
#include <stack>
using namespace std;
class SpecialStack {
stack<int> st;
stack<int> maxStack;
public:
// Add an element to the stack
void push(int x) {
st.push(x);
// Maintain maxStack for O(1) getMax()
if (maxStack.empty() || x >= maxStack.top())
maxStack.push(x);
else
maxStack.push(maxStack.top());
}
// Remove the top element from the stack
int pop() {
if (st.empty()) return -1;
int top = st.top();
st.pop();
maxStack.pop();
return top;
}
// Get the top element
int peek() {
return st.empty() ? -1 : st.top();
}
// Check if stack is empty
bool isEmpty() {
return st.empty();
}
// Get the maximum element
int getMax() {
return maxStack.empty() ? -1 : maxStack.top();
}
};
int main() {
SpecialStack st;
st.push(18);
st.push(19);
st.push(29);
st.push(15);
cout << st.peek() << "\n";
cout << st.getMax() << "\n";
st.push(16);
cout << st.pop() << "\n";
cout << st.pop() << "\n";
cout << st.getMax() << "\n";
return 0;
}
import java.util.Stack;
class SpecialStack {
Stack<Integer> st = new Stack<>();
Stack<Integer> maxSt = new Stack<>();
public void push(int x) {
st.push(x);
// If maxSt is empty or x is greater than top of maxSt, push x
if (maxSt.isEmpty() || x >= maxSt.peek()) {
maxSt.push(x);
} else {
// Otherwise, push the top of maxSt to keep maximum unchanged
maxSt.push(maxSt.peek());
}
}
// Pop the top element
public int pop() {
if (st.isEmpty()) {
return -1;
}
int poppedElement = st.pop();
maxSt.pop();
return poppedElement;
}
// Return the top element without removing
public int peek() {
return st.isEmpty() ? -1 : st.peek();
}
// Check if the stack is empty
public boolean isEmpty() {
return st.isEmpty();
}
// Return maximum element
public int getMax() {
return maxSt.isEmpty() ? -1 : maxSt.peek();
}
public static void main(String[] args) {
SpecialStack st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
System.out.println(st.peek());
System.out.println(st.getMax());
st.push(16);
System.out.println(st.pop());
System.out.println(st.pop());
System.out.println(st.getMax());
}
}
class SpecialStack:
def __init__(self):
self.st = []
self.maxSt = []
def push(self, x):
self.st.append(x)
# If maxSt is empty or x is greater than top of maxSt, push x
if not self.maxSt or x >= self.maxSt[-1]:
self.maxSt.append(x)
else:
# Otherwise, push the top of maxSt to keep maximum unchanged
self.maxSt.append(self.maxSt[-1])
# Pop the top element
def pop(self):
if not self.st:
return -1
poppedElement = self.st.pop()
self.maxSt.pop()
return poppedElement
# Return the top element without removing
def peek(self):
return -1 if not self.st else self.st[-1]
# Check if the stack is empty
def isEmpty(self):
return len(self.st) == 0
# Return maximum element
def getMax(self):
return -1 if not self.maxSt else self.maxSt[-1]
if __name__ == "__main__":
st = SpecialStack()
st.push(18)
st.push(19)
st.push(29)
st.push(15)
print(st.peek())
print(st.getMax())
st.push(16)
print(st.pop())
print(st.pop())
print(st.getMax())
using System;
using System.Collections.Generic;
class SpecialStack {
Stack<int> st = new Stack<int>();
Stack<int> maxSt = new Stack<int>();
public void push(int x) {
st.Push(x);
// If maxSt is empty or x is greater than top of maxSt, push x
if (maxSt.Count == 0 || x >= maxSt.Peek()) {
maxSt.Push(x);
} else {
// Otherwise, push top of maxSt to keep maximum unchanged
maxSt.Push(maxSt.Peek());
}
}
// Pop the top element
public int pop() {
if (st.Count == 0) {
return -1;
}
int poppedElement = st.Peek();
st.Pop();
maxSt.Pop();
return poppedElement;
}
// Return the top element without removing
public int peek() {
return st.Count == 0 ? -1 : st.Peek();
}
// Check if the stack is empty
public bool isEmpty() {
return st.Count == 0;
}
// Return maximum element
public int getMax() {
return maxSt.Count == 0 ? -1 : maxSt.Peek();
}
static void Main(string[] args) {
SpecialStack st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
Console.WriteLine(st.peek());
Console.WriteLine(st.getMax());
st.push(16);
Console.WriteLine(st.pop());
Console.WriteLine(st.pop());
Console.WriteLine(st.getMax());
}
}
class SpecialStack {
constructor() {
this.st = [];
this.maxSt = [];
}
push(x) {
this.st.push(x);
// If maxSt is empty or x is greater than top of maxSt, push x
if (this.maxSt.length === 0 || x >= this.maxSt[this.maxSt.length - 1]) {
this.maxSt.push(x);
} else {
// Otherwise, push top of maxSt to keep maximum unchanged
this.maxSt.push(this.maxSt[this.maxSt.length - 1]);
}
}
// Pop the top element
pop() {
if (this.st.length === 0) {
return -1;
}
let poppedElement = this.st[this.st.length - 1];
this.st.pop();
this.maxSt.pop();
return poppedElement;
}
// Return the top element without removing
peek() {
return this.st.length === 0 ? -1 : this.st[this.st.length - 1];
}
// Check if the stack is empty
isEmpty() {
return this.st.length === 0;
}
// Return maximum element
getMax() {
return this.maxSt.length === 0 ? -1 : this.maxSt[this.maxSt.length - 1];
}
}
// Driver Code
const st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
console.log(st.peek());
console.log(st.getMax());
st.push(16);
console.log(st.pop());
console.log(st.pop());
console.log(st.getMax());
Output
15 29 16 15 29
[Approach 2] Using a Pair in Stack - O(1) Time and O(n) Space
The idea is to use a single stack where each entry is stored as a pair. The first part of the pair represents the actual element, while the second part keeps track of the maximum value in the stack up to that point. This way, every push automatically updates the running maximum, and the top of the stack always gives us direct access to the current maximum.
How to Maintain Maximum Element?
Whenever we push an element:
- If the stack is empty, we push (element, element) since the element itself is the maximum.
- Otherwise, we compare the new element with the current maximum (the second value at the top of the stack). The new pair becomes (element, max(element, currentMax)).
Whenever we pop, we simply remove the top pair. With this structure, the top of the stack not only gives the current element but also the maximum till that point. Hence, getMax() is just returning the second value of the top pair, which works in constant time.
#include <iostream>
#include <stack>
using namespace std;
class SpecialStack {
// Each element stores {value, maximum till now}
stack<pair<int, int>> st;
public:
// Push element onto the stack
void push(int x) {
if (st.empty()) {
// If stack is empty, both value and maxTillNow are x
st.push({x, x});
} else {
// Otherwise, compute the new maxTillNow
int currentMax = max(x, st.top().second);
st.push({x, currentMax});
}
}
// Pop the top element from the stack
int pop() {
if (st.empty()) {
return -1;
}
int poppedElement = st.top().first;
st.pop();
return poppedElement;
}
// Return the top element of the stack
int peek() {
if (st.empty()) {
return -1;
}
return st.top().first;
}
// Check if the stack is empty
bool isEmpty() {
return st.empty();
}
// Get the maximum element in the stack
int getMax() {
if (st.empty()) {
return -1;
}
return st.top().second;
}
};
int main() {
SpecialStack st;
st.push(18);
st.push(19);
st.push(29);
st.push(15);
cout << st.peek() << "\n";
cout << st.getMax() << "\n";
st.push(16);
cout << st.pop() << "\n";
cout << st.pop() << "\n";
cout << st.getMax() << "\n";
return 0;
}
import java.util.Stack;
class SpecialStack {
// Each element stores {value, maximum till now}
private Stack<int[]> st = new Stack<>();
// Push element onto the stack
public void push(int x) {
if (st.isEmpty()) {
// If stack is empty, both value and maxTillNow are x
st.push(new int[]{x, x});
} else {
// Otherwise, compute the new maxTillNow
int currentMax = Math.max(x, st.peek()[1]);
st.push(new int[]{x, currentMax});
}
}
// Pop the top element from the stack
public int pop() {
if (st.isEmpty()) {
return -1;
}
int poppedElement = st.peek()[0];
st.pop();
return poppedElement;
}
// Return the top element of the stack
public int peek() {
if (st.isEmpty()) {
return -1;
}
return st.peek()[0];
}
// Check if the stack is empty
public boolean isEmpty() {
return st.isEmpty();
}
// Get the maximum element in the stack
public int getMax() {
if (st.isEmpty()) {
return -1;
}
return st.peek()[1];
}
public static void main(String[] args) {
SpecialStack st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
System.out.println(st.peek());
System.out.println(st.getMax());
st.push(16);
System.out.println(st.pop());
System.out.println(st.pop());
System.out.println(st.getMax());
}
}
class SpecialStack:
# Each element stores (value, maximum till now)
def __init__(self):
self.st = []
# Push element onto the stack
def push(self, x):
if not self.st:
# If stack is empty, both value and maxTillNow are x
self.st.append((x, x))
else:
# Otherwise, compute the new maxTillNow
currentMax = max(x, self.st[-1][1])
self.st.append((x, currentMax))
# Pop the top element from the stack
def pop(self):
if not self.st:
return -1
poppedElement = self.st[-1][0]
self.st.pop()
return poppedElement
# Return the top element of the stack
def peek(self):
if not self.st:
return -1
return self.st[-1][0]
# Check if the stack is empty
def isEmpty(self):
return len(self.st) == 0
# Get the maximum element in the stack
def getMax(self):
if not self.st:
return -1
return self.st[-1][1]
if __name__ == "__main__":
st = SpecialStack()
st.push(18)
st.push(19)
st.push(29)
st.push(15)
print(st.peek())
print(st.getMax())
st.push(16)
print(st.pop())
print(st.pop())
print(st.getMax())
using System;
using System.Collections.Generic;
class SpecialStack {
// Each element in the stack stores (value, maximum till now)
private Stack<(int, int)> st = new Stack<(int, int)>();
// Push element onto the stack
public void push(int x) {
if (st.Count == 0) {
// If stack is empty, both value and maxTillNow are x
st.Push((x, x));
} else {
// Otherwise, compute the new maxTillNow
int currentMax = Math.Max(x, st.Peek().Item2);
st.Push((x, currentMax));
}
}
// Pop the top element from the stack
public int pop() {
if (st.Count == 0) {
return -1;
}
int poppedElement = st.Peek().Item1;
st.Pop();
return poppedElement;
}
// Return the top element of the stack
public int peek() {
if (st.Count == 0) {
return -1;
}
return st.Peek().Item1;
}
// Check if the stack is empty
public bool isEmpty() {
return st.Count == 0;
}
// Get the maximum element in the stack
public int getMax() {
if (st.Count == 0) {
return -1;
}
return st.Peek().Item2;
}
public static void Main(string[] args) {
SpecialStack st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
Console.WriteLine(st.peek());
Console.WriteLine(st.getMax());
st.push(16);
Console.WriteLine(st.pop());
Console.WriteLine(st.pop());
Console.WriteLine(st.getMax());
}
}
class SpecialStack {
constructor() {
// Each element in the stack stores [value, maximum till now]
this.st = [];
}
// Push element onto the stack
push(x) {
if (this.st.length === 0) {
// If stack is empty, both value and maxTillNow are x
this.st.push([x, x]);
} else {
// Otherwise, compute the new maxTillNow
let currentMax = Math.max(x, this.st[this.st.length - 1][1]);
this.st.push([x, currentMax]);
}
}
// Pop the top element from the stack
pop() {
if (this.st.length === 0) return -1;
let poppedElement = this.st[this.st.length - 1][0];
this.st.pop();
return poppedElement;
}
// Return the top element of the stack
peek() {
if (this.st.length === 0) return -1;
return this.st[this.st.length - 1][0];
}
// Check if the stack is empty
isEmpty() {
return this.st.length === 0;
}
// Get the maximum element in the stack
getMax() {
if (this.st.length === 0) return -1;
return this.st[this.st.length - 1][1];
}
}
// Driver Code
let st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
console.log(st.peek());
console.log(st.getMax());
st.push(16);
console.log(st.pop());
console.log(st.pop());
console.log(st.getMax());
Output
15 29 16 15 29
[Expected Approach] Using Mathematical Encoding - O(1) Time and O(1) Space
The idea is to maintain a single variable for the current maximum and encode any new element that is greater than the current maximum before pushing it onto the stack. This encoded value allows us to store both the new element and the previous maximum in one number. While popping, if the value is encoded, we decode it to retrieve the previous maximum, ensuring that getMax() always returns the correct maximum in constant time without using extra space.
There are two main steps to handle the maximum while using the encoding approach:
Case 1: While pushing a value
- If the new element x is less than or equal to the current maximum (maxElement), push it normally.
- If x is greater than maxElement, encode it before pushing: encoded = (2 * x − maxElement) and Push encoded onto the stack.
- Update maxElement = x to reflect the new maximum.
Case 2: While popping a value
- If the popped value is less than or equal to maxElement, it is a normal value.
- If the popped value is greater than maxElement, it is an encoded value.
- Retrieve the previous maximum using: previousMax = (2 * maxElement − encoded) and update maxElement = previousMax.
#include <iostream>
#include <stack>
using namespace std;
class SpecialStack {
stack<int> st;
int maxElement;
public:
void push(int x) {
if (st.empty()) {
st.push(x);
maxElement = x;
} else if (x <= maxElement) {
st.push(x);
} else {
// Encode the value and update maxElement
int encoded = 2 * x - maxElement;
st.push(encoded);
maxElement = x;
}
}
// Pop the top element from the stack
int pop() {
if (st.empty()) return -1;
int topVal = st.top();
st.pop();
if (topVal <= maxElement) {
return topVal;
} else {
int actualValue = maxElement;
int previousMax = 2 * maxElement - topVal;
maxElement = previousMax;
return actualValue;
}
}
// Return the top element of the stack
int peek() {
if (st.empty()) return -1;
int topVal = st.top();
return (topVal <= maxElement) ? topVal : maxElement;
}
// Check if the stack is empty
bool isEmpty() {
return st.empty();
}
// Get the maximum element in the stack
int getMax() {
if (st.empty()) return -1;
return maxElement;
}
};
int main() {
SpecialStack st;
st.push(18);
st.push(19);
st.push(29);
st.push(15);
cout << st.peek() << "\n";
cout << st.getMax() << "\n";
st.push(16);
cout << st.pop() << "\n";
cout << st.pop() << "\n";
cout << st.getMax() << "\n";
return 0;
}
import java.util.Stack;
class SpecialStack {
Stack<Integer> st = new Stack<>();
int maxElement;
// Push element onto the stack
void push(int x) {
if (st.isEmpty()) {
st.push(x);
maxElement = x;
} else if (x <= maxElement) {
st.push(x);
} else {
// Encode the value and update maxElement
int encoded = 2 * x - maxElement;
st.push(encoded);
maxElement = x;
}
}
// Pop the top element from the stack
int pop() {
if (st.isEmpty()) return -1;
int topVal = st.pop();
if (topVal <= maxElement) {
return topVal;
} else {
int actualValue = maxElement;
int previousMax = 2 * maxElement - topVal;
maxElement = previousMax;
return actualValue;
}
}
// Return the top element of the stack
int peek() {
if (st.isEmpty()) return -1;
int topVal = st.peek();
return (topVal <= maxElement) ? topVal : maxElement;
}
// Check if the stack is empty
boolean isEmpty() {
return st.isEmpty();
}
// Get the maximum element in the stack
int getMax() {
if (st.isEmpty()) return -1;
return maxElement;
}
public static void main(String[] args) {
SpecialStack st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
System.out.println(st.peek());
System.out.println(st.getMax());
st.push(16);
System.out.println(st.pop());
System.out.println(st.pop());
System.out.println(st.getMax());
}
}
class SpecialStack:
def __init__(self):
self.st = []
self.maxElement = None
# Push element onto the stack
def push(self, x):
if not self.st:
self.st.append(x)
self.maxElement = x
elif x <= self.maxElement:
self.st.append(x)
else:
# Encode the value and update maxElement
encoded = 2 * x - self.maxElement
self.st.append(encoded)
self.maxElement = x
# Pop the top element from the stack
def pop(self):
if not self.st:
return -1
topVal = self.st.pop()
if topVal <= self.maxElement:
return topVal
else:
actualValue = self.maxElement
previousMax = 2 * self.maxElement - topVal
self.maxElement = previousMax
return actualValue
# Return the top element of the stack
def peek(self):
if not self.st:
return -1
topVal = self.st[-1]
return topVal if topVal <= self.maxElement else self.maxElement
# Check if the stack is empty
def isEmpty(self):
return len(self.st) == 0
# Get the maximum element in the stack
def getMax(self):
if not self.st:
return -1
return self.maxElement
if __name__ == "__main__":
st = SpecialStack()
st.push(18)
st.push(19)
st.push(29)
st.push(15)
print(st.peek())
print(st.getMax())
st.push(16)
print(st.pop())
print(st.pop())
print(st.getMax())
using System;
using System.Collections.Generic;
class SpecialStack {
Stack<int> st = new Stack<int>();
int maxElement;
// Push element onto the stack
public void push(int x) {
if (st.Count == 0) {
st.Push(x);
maxElement = x;
} else if (x <= maxElement) {
st.Push(x);
} else {
// Encode the value and update maxElement
int encoded = 2 * x - maxElement;
st.Push(encoded);
maxElement = x;
}
}
// Pop the top element from the stack
public int pop() {
if (st.Count == 0) {
return -1;
}
int topVal = st.Pop();
if (topVal <= maxElement) {
return topVal;
} else {
int actualValue = maxElement;
int previousMax = 2 * maxElement - topVal;
maxElement = previousMax;
return actualValue;
}
}
// Return the top element of the stack
public int peek() {
if (st.Count == 0) {
return -1;
}
int topVal = st.Peek();
return (topVal <= maxElement) ? topVal : maxElement;
}
// Check if the stack is empty
public bool IsEmpty() {
return st.Count == 0;
}
// Get the maximum element in the stack
public int getMax() {
if (st.Count == 0) {
return -1;
}
return maxElement;
}
public static void Main() {
SpecialStack st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
Console.WriteLine(st.peek());
Console.WriteLine(st.getMax());
st.push(16);
Console.WriteLine(st.pop());
Console.WriteLine(st.pop());
Console.WriteLine(st.getMax());
}
}
class SpecialStack {
constructor() {
this.st = [];
this.maxElement = null;
}
// Push element onto the stack
push(x) {
if (this.st.length === 0) {
this.st.push(x);
this.maxElement = x;
} else if (x <= this.maxElement) {
this.st.push(x);
} else {
// Encode the value and update maxElement
let encoded = 2 * x - this.maxElement;
this.st.push(encoded);
this.maxElement = x;
}
}
// Pop the top element from the stack
pop() {
if (this.st.length === 0) {
return -1;
}
let topVal = this.st.pop();
if (topVal <= this.maxElement) {
return topVal;
} else {
let actualValue = this.maxElement;
let previousMax = 2 * this.maxElement - topVal;
this.maxElement = previousMax;
return actualValue;
}
}
// Return the top element of the stack
peek() {
if (this.st.length === 0) {
return -1;
}
let topVal = this.st[this.st.length - 1];
return topVal <= this.maxElement ? topVal : this.maxElement;
}
// Check if the stack is empty
isEmpty() {
return this.st.length === 0;
}
// Get the maximum element in the stack
getMax() {
if (this.st.length === 0) {
return -1;
}
return this.maxElement;
}
}
// Driver Code
let st = new SpecialStack();
st.push(18);
st.push(19);
st.push(29);
st.push(15);
console.log(st.peek());
console.log(st.getMax());
st.push(16);
console.log(st.pop());
console.log(st.pop());
console.log(st.getMax());
Output
15 29 16 15 29