Stacks are particularly useful for problems involving matching, nesting, or processing elements in reverse order.
Decode String
Given an encoded string like "3[a]2[bc]", return its decoded string "aaabcbc".
The encoding rule is: k[encoded_string], where the encoded string inside brackets is repeated exactly k times.
Example
- Input: s = “3[a]2[bc]”
- Output: “aaabcbc”
Solution
def stacky(s):
"""
When we hit an open bracket, we know we have parsed k for the contents of the bracket,
so push (current_string, k) to the stack, so we can pop them on closing bracket
to duplicate the enclosed string k times.
"""
stack = []
current_string = ""
k = 0
for char in s:
if char == "[":
# Just finished parsing this k, save current_string and k for
# when we pop
stack.append((current_string, k))
# Reset current_string and k for this new frame
current_string = ""
k = 0
elif char == "]":
# We have completed this frame, get the last current_string and k
# from when the frame opened, which is the k we need to duplicate
# the current current_string by
last_string, last_k = stack.pop(-1)
current_string = last_string + last_k * current_string
elif char.isdigit():
k = k * 10 + int(char)
else:
current_string += char
return current_string
Generate Parentheses
Given n pairs of parentheses, generate all combinations of well-formed parentheses.
Example
- Input: n = 3
- Output: [”((()))”,”(()())”,”(())()”,”()(())”,”()()()”]
Brute Force Approach
def valid(self, s: str):
open = 0
for c in s:
open = (open+1) if c=="(" else (open-1)
if open < 0:
# We have a closing parantheses that we cannot match..
return False
return (open == 0)
def generateParenthesis(self, n: int) -> List[str]:
res = []
def dfs(s):
if len(s) == 2*n:
if valid(s): # We dont want ELSE to run if valid(s) is false.
res.append(s)
else:
dfs(s + "(")
dfs(s + ")")
dfs("")
return res
Optimized Backtracking Approach
def generateParenthesis(self, n: int) -> List[str]:
stack = []
res = []
def dfs(openN, closedN):
if openN == closedN == n:
res.append("".join(stack))
else:
if openN < n:
stack.append("(")
dfs(openN+1, closedN)
stack.pop()
if closedN < openN <= n:
stack.append(")")
dfs(openN, closedN+1)
stack.pop()
dfs(0, 0)
return res
Monotonic Stacks
Monotonic Stacks are used when you need to traverse an array with a fixed pointer, but also need to efficiently backtrack to smaller/larger elements than the current element without moving your pointer.
Next Greater Element
Given nums, find the next greater element for each element in the array. If an element does not have a next greater element, use -1.
def nextGreaterElement(nums):
n = len(nums)
result = [-1] * n
stack = []
for i in range(n):
while stack and nums[i] > nums[stack[-1]]:
idx = stack.pop()
result[idx] = nums[i]
stack.append(i)
return result
Daily Temperatures
Return an array where each element is the number of days you need to wait for a warmer temperature.
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
stack = [] # holds indexes, not values
res = [0] * len(temperatures)
for i, temp in enumerate(temperatures):
while stack and temperatures[stack[-1]] < temp:
res[stack[-1]] = (i - stack[-1]) # distance
stack.pop()
stack.append(i)
return res