Stack Data Structure
Table of contents
- Nigerian Banking Story for Context:
- Real-World Applications of Stacks in Banking:
- Problem Example:
- Code Example:
- Output:
- Practice Problem:
- Solution for Practice Problem:
- Output:
- For Turing interview coding challenges, questions often test practical understanding of data structures and algorithms. Below are stack-related questions and solutions similar to what you might encounter during a Turing coding test.
- 1. Question: Valid Parentheses
- 2. Question: Implement a Min Stack
- 3. Question: Reverse a String Using a Stack
- 4. Question: Next Greater Element
- 5. Question: Evaluate Reverse Polish Notation (RPN)
A stack is a linear data structure that follows the Last In, First Out (LIFO) principle. The last item added to the stack is the first one removed. It supports the following primary operations:
Push: Add an element to the top of the stack.
Pop: Remove the top element from the stack.
Peek: View the top element without removing it.
IsEmpty: Check if the stack is empty.
Nigerian Banking Story for Context:
Consider a Nigerian bank managing customer transaction reversals. When customers make complaints about failed or incorrect transactions, the bank logs each reversal request into a stack. The most recent reversal request is processed first to prioritize recent issues while ensuring fairness in handling complaints.
For example:
A customer complains about an ATM withdrawal failure.
Another customer complains about a wrong POS debit.
The bank logs these complaints as tasks into a stack.
The bank processes the latest issue first (LIFO), ensuring efficient issue resolution for all customers.
Real-World Applications of Stacks in Banking:
Transaction Reversals: Handling customer complaints in the order of urgency, where the most recent issue is prioritized.
Undo/Redo in Online Banking: Allowing customers to revert or redo recent actions (e.g., undoing fund transfers).
Audit Logs: Tracking recent activities and reversing them if errors are detected.
Problem Example:
Scenario:
A Nigerian bank needs to manage transaction complaints. Write a program to:
Add new complaints to the stack.
Remove the most recent complaint when resolved.
Display the current top complaint without removing it.
Input:
- A series of complaints: "Failed ATM withdrawal," "POS double debit," "Wrong account transfer."
Output:
The stack after all complaints are added.
The top complaint after resolving the most recent one.
The stack after resolving another complaint.
Code Example:
class BankComplaintStack:
def __init__(self):
self.stack = []
# Add a complaint to the stack
def push(self, complaint):
self.stack.append(complaint)
print(f"Complaint logged: {complaint}")
# Remove the top complaint from the stack
def pop(self):
if self.is_empty():
return "No complaints to resolve."
complaint = self.stack.pop()
print(f"Complaint resolved: {complaint}")
return complaint
# View the top complaint
def peek(self):
if self.is_empty():
return "No complaints in the system."
return f"Current complaint: {self.stack[-1]}"
# Check if the stack is empty
def is_empty(self):
return len(self.stack) == 0
# Display all complaints
def display(self):
return f"Current stack: {self.stack}" if not self.is_empty() else "No complaints in the system."
# Initialize the complaint stack
bank_complaints = BankComplaintStack()
# Add complaints
bank_complaints.push("Failed ATM withdrawal")
bank_complaints.push("POS double debit")
bank_complaints.push("Wrong account transfer")
# Display current stack
print(bank_complaints.display())
# View the top complaint
print(bank_complaints.peek())
# Resolve the most recent complaint
bank_complaints.pop()
# Display updated stack
print(bank_complaints.display())
# Resolve another complaint
bank_complaints.pop()
# Display final stack
print(bank_complaints.display())
Output:
Complaint logged: Failed ATM withdrawal
Complaint logged: POS double debit
Complaint logged: Wrong account transfer
Current stack: ['Failed ATM withdrawal', 'POS double debit', 'Wrong account transfer']
Current complaint: Wrong account transfer
Complaint resolved: Wrong account transfer
Current stack: ['Failed ATM withdrawal', 'POS double debit']
Complaint resolved: POS double debit
Current stack: ['Failed ATM withdrawal']
Practice Problem:
Scenario:
The bank now wants to extend this system to allow branch managers to review pending complaints and undo resolved ones if needed.
Task:
Implement a function to allow undoing resolved complaints by storing resolved complaints in a separate stack.
Add a function to redo an undone complaint.
Solution for Practice Problem:
class UndoRedoComplaintSystem:
def __init__(self):
self.complaint_stack = []
self.resolved_stack = []
# Log a new complaint
def log_complaint(self, complaint):
self.complaint_stack.append(complaint)
print(f"Complaint logged: {complaint}")
# Resolve the latest complaint
def resolve_complaint(self):
if not self.complaint_stack:
return "No complaints to resolve."
complaint = self.complaint_stack.pop()
self.resolved_stack.append(complaint)
return f"Resolved complaint: {complaint}"
# Undo the last resolved complaint
def undo_resolution(self):
if not self.resolved_stack:
return "No resolved complaints to undo."
complaint = self.resolved_stack.pop()
self.complaint_stack.append(complaint)
return f"Resolution undone for: {complaint}"
# Display all pending complaints
def display_complaints(self):
return f"Pending complaints: {self.complaint_stack}" if self.complaint_stack else "No pending complaints."
# Display resolved complaints
def display_resolved(self):
return f"Resolved complaints: {self.resolved_stack}" if self.resolved_stack else "No resolved complaints."
# Test the UndoRedo functionality
bank_system = UndoRedoComplaintSystem()
# Log complaints
bank_system.log_complaint("Failed ATM withdrawal")
bank_system.log_complaint("POS double debit")
# Resolve a complaint
print(bank_system.resolve_complaint())
# Undo the resolution
print(bank_system.undo_resolution())
# Display all complaints
print(bank_system.display_complaints())
print(bank_system.display_resolved())
Output:
Complaint logged: Failed ATM withdrawal
Complaint logged: POS double debit
Resolved complaint: POS double debit
Resolution undone for: POS double debit
Pending complaints: ['Failed ATM withdrawal', 'POS double debit']
Resolved complaints: []
For Turing interview coding challenges, questions often test practical understanding of data structures and algorithms. Below are stack-related questions and solutions similar to what you might encounter during a Turing coding test.
1. Question: Valid Parentheses
Problem:
Given a string s
containing just the characters '('
, ')'
, '{'
, '}'
, '['
, and ']'
, determine if the input string is valid.
A string is valid if:
Open brackets are closed by the same type of brackets.
Open brackets are closed in the correct order.
Example:
Input: s = "()"
Output: true
Input: s = "([)]"
Output: false
Input: s = "{[]}"
Output: true
Solution:
def is_valid_parentheses(s):
stack = []
mapping = {')': '(', '}': '{', ']': '['}
for char in s:
if char in mapping:
top_element = stack.pop() if stack else '#'
if mapping[char] != top_element:
return False
else:
stack.append(char)
return not stack
# Test Cases
print(is_valid_parentheses("()")) # Output: True
print(is_valid_parentheses("([)]")) # Output: False
print(is_valid_parentheses("{[]}")) # Output: True
Explanation:
Use a stack to keep track of opening brackets.
When encountering a closing bracket, check if it matches the last opening bracket in the stack. If not, return
False
.At the end, ensure the stack is empty.
2. Question: Implement a Min Stack
Problem:
Design a stack that supports the following operations in O(1) time:
push(x)
— Push element x onto the stack.pop()
— Remove the element on top of the stack.top()
— Get the top element.get_min()
— Retrieve the minimum element in the stack.
Example:
Input:
push(-2), push(0), push(-3), get_min(), pop(), top(), get_min()
Output:
-3, 0, -2
Solution:
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
def push(self, x):
self.stack.append(x)
if not self.min_stack or x <= self.min_stack[-1]:
self.min_stack.append(x)
def pop(self):
if self.stack.pop() == self.min_stack[-1]:
self.min_stack.pop()
def top(self):
return self.stack[-1] if self.stack else None
def get_min(self):
return self.min_stack[-1] if self.min_stack else None
# Test Case
min_stack = MinStack()
min_stack.push(-2)
min_stack.push(0)
min_stack.push(-3)
print(min_stack.get_min()) # Output: -3
min_stack.pop()
print(min_stack.top()) # Output: 0
print(min_stack.get_min()) # Output: -2
Explanation:
Maintain two stacks:
stack
: Stores all elements.min_stack
: Tracks the minimum value at each stage.
3. Question: Reverse a String Using a Stack
Problem:
Write a function that reverses a string using a stack.
Example:
Input: "hello"
Output: "olleh"
Solution:
def reverse_string(s):
stack = []
for char in s:
stack.append(char)
reversed_str = ""
while stack:
reversed_str += stack.pop()
return reversed_str
# Test Case
print(reverse_string("hello")) # Output: "olleh"
Explanation:
Push each character onto the stack.
Pop characters to build the reversed string (LIFO behavior ensures reversal).
4. Question: Next Greater Element
Problem:
Given an array, find the next greater element for each element in the array.
The next greater element for an element x
is the first greater element on its right. If it does not exist, output -1
.
Example:
Input: [4, 5, 2, 10]
Output: [5, 10, 10, -1]
Solution:
def next_greater_element(nums):
stack = []
result = [-1] * len(nums)
for i in range(len(nums)):
while stack and nums[stack[-1]] < nums[i]:
index = stack.pop()
result[index] = nums[i]
stack.append(i)
return result
# Test Case
print(next_greater_element([4, 5, 2, 10])) # Output: [5, 10, 10, -1]
Explanation:
Traverse the array.
Use a stack to keep track of indices of elements for which the next greater element has not been found.
Update the result for elements whose next greater element is identified.
5. Question: Evaluate Reverse Polish Notation (RPN)
Problem:
Evaluate an arithmetic expression in Reverse Polish Notation.
Example:
Input: tokens = ["2", "1", "+", "3", "*"]
Output: 9
Explanation: ((2 + 1) * 3) = 9
Solution:
def eval_rpn(tokens):
stack = []
for token in tokens:
if token not in "+-*/":
stack.append(int(token))
else:
b = stack.pop()
a = stack.pop()
if token == '+':
stack.append(a + b)
elif token == '-':
stack.append(a - b)
elif token == '*':
stack.append(a * b)
elif token == '/':
stack.append(int(a / b)) # Ensure integer division
return stack[0]
# Test Case
print(eval_rpn(["2", "1", "+", "3", "*"])) # Output: 9
Explanation:
Push operands onto the stack.
When encountering an operator, pop two operands, perform the operation, and push the result back.