Stack and Queue Questions Part 1 ================================ 173. Print elements of a linked list --------------------------------------- | Using a stack to print the entries of a singly linked list. | T O(N), S O(N). | (We could also use the technique of reversing a linked list, then T O(N), S O(1).) :: def print_linked_list_in_reverse(head): nodes = [] while head: nodes.append(head.data) head = head.next while nodes: print(nodes.pop()) 174. (LC 155) Min Stack ---------------------------- `155. Min Stack `_ Medium | We can improve on space: | -if a new element pushed to stack is greater then current max, then we don't have to | add it to minStack (it will never be min). | -record min and min_count. So if elem=5 and we encounter a second 5, we record count+=1 | for elem 5. E.g.: | stack = [2,2,1,4] | minStack = [(2,1), (2,2), (4,1)] | #can be tuples, named tuples, or class within our Stack class. **Solution 1** [:ref:`10 `] [:ref:`7 `] T O(1), S O(N) :: class MinStack: def __init__(self): self.stack = [] self.minStack = [] def push(self, val: int) -> None: self.stack.append(val) val = min(val, self.minStack[-1] if self.minStack else val) self.minStack.append(val) def pop(self) -> None: self.stack.pop() self.minStack.pop() def top(self) -> int: return self.stack[-1] def getMin(self) -> int: return self.minStack[-1] 175. (LC 150) Evaluate Reverse Polish Notation -------------------------------------------------- `LC 150. Evaluate Reverse Polish Notation `_ Medium | RPN (Reverse Polish Notation): | Example 1: | Input: ``tokens = ["2","1","+","3","*"]`` | Output: 9 | Explanation: ((2 + 1) * 3) = 9 | Ex 2: | Input: ``tokens = ["4","13","5","/","+"]`` | Output: 6 | Explanation: (4 + (13 / 5)) = 6 | **Recognize the simplicity of the RPN mechanism:** | No matter what form of RPN you are given, even if [num, num, num, oper, oper], | the procedure is the same: | -the partial results are added and removed in LIFO order, so use stack | -if cur char == num: then add to stack | if cur char == operator: then pop the last two nums from stack and use that operator on them, | add result to stack. | Approaches: | The differences in implementation concern only the way you implement the operators: | -don't implement, use directly +,-,*,/ | -use hash, map using lambda | -use hash, map to operator module functions | -the last entry in stack is the result of the last expression = our answer .. admonition:: Recall: passing args to lambda a = lambda x, y: x + y print(a(2, 3)) **Solution 1** [:ref:`2 `] (LC accepted 95,85%) :: stack = [] # intermidiate results DELIMITER = "," OPERATORS = { "+": lambda x, y: x + y, "-": lambda x, y: y - x, "*": lambda y, x: x * y, "/": lambda x, y: int(y / x), } for token in tokens.split(DELIMITER): if token in OPERATORS: stack.append(OPERATORS[token](stack.pop(), stack.pop())) else: # token is a num stack.append(int(token)) return stack[-1] **Solution 2** [:ref:`7 `] :: import operator class Solution: def evalRPN(self, tokens: List[str]) -> int: opt = { "+": operator.add, "-": operator.sub, "*": operator.mul, "/": operator.truediv, } s = [] for token in tokens: if token in opt: s.append(int(opt[token](s.pop(-2), s.pop(-1)))) else: s.append(int(token)) return s[0] **Solution 3** [:ref:`10 `] O(N) :: class Solution: def evalRPN(self, tokens: List[str]) -> int: stack = [] for c in tokens: if c == "+": stack.append(stack.pop() + stack.pop()) elif c == "-": a, b = stack.pop(), stack.pop() stack.append(b - a) elif c == "*": stack.append(stack.pop() * stack.pop()) elif c == "/": a, b = stack.pop(), stack.pop() stack.append(int(float(b) / a)) else: stack.append(int(c)) return stack[0] **My V** (LC accepted 48,85%) :: class Solution: def evalRPN(self, tokens: List[str]) -> int: operators = {'+': operator.add, '-': operator.sub, '*': operator.mul, '/': operator.truediv} stack = [] for s in tokens: if s in operators: val1 = stack.pop() val2 = stack.pop() val3 = operators[s](val2, val1) stack.append(int(val3)) else: stack.append(int(s)) return stack[0] 176. (LC 71) Simplify Path ------------------------------ `71. Simplify Path `_ Medium | **Key:** | -Understand the task, it is simple. | When given a path, it is a series of cd commands in the shell. | E.g. path = '/../abc//./def/' | 0)We always start at the root. | 1) cd .. (we are at the root, so .. does nothing.) | 2) cd abc (we are at /abc) | 3) ignore //, ignore . | 4) cd def (we are at /abc/def) | Note: we always ignore //, and . (cd to current) | -Why stack | Because of .. | When we encounter .. we go back one dir, so pop from stack ONCE. Our new path IS STACK itself (stack is not a helper data structure). **Solution 1** :: class Solution: def simplifyPath(self, path: str) -> str: stack = [] for i in path.split("/"): # if i == "/" or i == '//', it becomes '' (empty string) if i == "..": if stack: stack.pop() elif i == "." or i == '': # skip "." or an empty string continue else: stack.append(i) res = "/" + "/".join(stack) return res | Note when splitting on '/', we get empty ``''``: | #1 >>> path = '/../ed//tr/' >>> L = path.split("/") >>> L ['', '..', 'ed', '', 'tr', ''] | #2 | To stack we just add, e.g. ``stack = ['ed', 'tr']`` **My V** (LC accepted 85, 37%) :: class Solution: def simplifyPath(self, path: str) -> str: stack = [] path = path.split('/') for p in path: if p == '.': continue elif p == '..': if stack: stack.pop() elif p != '/' and p != '': stack.append(p) new_path = '/'.join(stack) return '/' + new_path 177. (LC 22) Generate Parentheses ------------------------------------ `22. Generate Parentheses `_ Medium | **Background on Backtracking** | [`View the original article `_] | Backtracking is a technique for listing all possible solutions for a combinatorial algorithm problem. Backtracking is an algorithmic technique whose goal is to use brute force to find all solutions to a problem. It entails gradually compiling a set of all possible solutions. Because a problem will have constraints, solutions that do not meet them will be removed. A backtracking algorithm uses the depth-first search method. If a proposed solution satisfies the constraints, it will keep looking. If it does not, the branch is removed, and . State-Space Tree A tree that represents all of the possible states of the problem, from the root as an initial state to the leaf as a terminal state. Intermediate checkpoints. If the checkpoints do not lead to a viable solution, the problem can return to the checkpoints and take another path to find a solution. **Backtracking boilerplate:** :: void FIND_SOLUTIONS(parameters): if (valid solution): store the solution Return for (all choice): if (valid choice): APPLY (choice) FIND_SOLUTIONS (parameters) BACKTRACK (remove choice) <=== Return | **Our alg in terms of backtracking** | So after finding and appending a valid solution. | res = ['((()))'] | We start to backtrack -> stack.pop() | Till | stack = ['(', '('] | And then start to collect another valid answer. | **Keys to our alg in general:** | How do you decide which ( or ) to add to response? | You keep track of the counts for the number of closing and opening parentheses. | Initially openCount=0, closeCount=0 | You deal with 2 cases: | 1)add '(' => if openCount < n | '((' | Put an open one any time, just not more than given times. | 2)add ')' => if openCount > closedCount | '(' -> '()', ')' -> No | Put closing one only if there are more open parentheses than closed ones. | I.e. there is a pending open one yet to close. Use variable where you build a current valid string | **Keys:** | -two ifs, not else | -add closed if num of open > num closed | -add open if num of open < total num of pairs **Solution 2** (rewrite from C, LC accepted 30, 15%, 2nd run 70, 80) :: class Solution: def generateParenthesis(self, n: int) -> List[str]: cur = '' res = [] def backtrack(cur, o, c): if o == n == c: res.append(cur) return # add open par if o < n: backtrack(cur + '(', o+1, c) # add closed if o > c: backtrack(cur + ')', o, c+1) backtrack(cur, 0,0) return res **Solution 1** :: class Solution: def generateParenthesis(self, n: int) -> List[str]: stack = [] res = [] def backtrack(openN, closedN): if openN == closedN == n: res.append("".join(stack)) return if openN < n: stack.append("(") backtrack(openN + 1, closedN) stack.pop() if closedN < openN: stack.append(")") backtrack(openN, closedN + 1) stack.pop() backtrack(0, 0) return res | Complexity Time O((2**n) * n) | Because we have 2 possibilities, closed or open (, ). 178. (LC 739) Daily Temperatures --------------------------------------- `739. Daily Temperatures `_ Medium | **Keys:** | -Stack, mono decreasing, storing pairs [temp, index] | Brute force would take us O(n**n). | Using monotonic stack: O(n) Time&Space. **Solution 1** [:ref:`10 `] :: class Solution: def dailyTemperatures(self, temperatures: List[int]) -> List[int]: res = [0] * len(temperatures) #1 stack = [] # pair: [temp, index] for i, t in enumerate(temperatures): while stack and t > stack[-1][0]: poppedT, poppedInd = stack.pop() res[stackInd] = i - stackInd stack.append((t, i)) return res | #1 | After iterating over all temps, if something is still in the stack, then there is | not a warmer day for these temps. Then the defaults of our ans 0 will be used. | If we encounter temperature : | -we pop from stack and | -calculate response for the index on stack | /When no stack, put current (temp, index) to stack. | **Going through the loop:** | Input: temperatures = [73,74,75,71,69,72,76,73] | Output: [1,1,4,2,1,1,0,0] | 1)(73,0) | stack=[] | res = [0,0,0,0,0,0,0] | stack=[(73,0)] | res = [0,0,0,0,0,0,0] | 2)(74,1) | stack=[(73,0)] | res = [0,0,0,0,0,0,0] | stack=[] | poppedT, pippedInd = 73, 0 | calculate res = curInd - poppedInd = 1-0=1 | At ind 0 = 1 | res = [1,0,0,0,0,0,0] stack=[(74,1)] | 3)(75,2) | stack=[(74,1)] | res = [1,0,0,0,0,0,0] | calculate res = curInd - poppedInd = 2-1=1 | res = [1,1,0,0,0,0,0] stack=[(75,2)] | 4)(71,3) | stack=[(75,2)] | Cur val 71 NOT > 75, then we just put it on stack. | stack=[(75,2), (71, 3)] | 5)(69,4) | Cur val 69 NOT > 71, then we put it on stack. | stack=[(75,2), (71, 3), (69, 4)] | 6)(72, 5) | stack=[(75,2), (71, 3), (69, 4)] | calculate res = curInd - poppedInd = 5-4=1 | At ind 4 = 1 | stack=[(75,2), (71, 3)] | res = [1,1,0,0,1,0,0] | still 72 > 71 | calculate res = curInd - poppedInd = 5-3=2 | At ind 3 = 2 | res = [1,1,0,2,1,0,0] | stack=[(75,2)] | 72 NOT > 75, add 72 to stack | stack=[(75,2), (72,5)] | 7)(76,6) | stack=[(75,2), (72,5)] | calculate res = curInd - poppedInd = 6-5=1 | At ind 5 = 1 | stack=[(75,2)] | res = [1,1,0,2,1,1,0] | still 76 > 75 | calculate res = curInd - poppedInd = 6-2=4 | At ind 2 = 4 | stack=[] | res = [1,1,4,2,1,1,0] | stack=[(76,6)] | 8)(73,7) | stack=[(76,6)] | 73 NOT > 76, so we just put it to stack | stack=[(76,6), (73,7)] 179. (LC 853) Car Fleet ------------------------- `853. Car Fleet `_ Medium | **My V** (LC accepted 65, 12) | In data: | position = [10, 8, 0, 5, 3] | speed = [2, 4, 1, 1, 3] | target = 12 | Make array cars = [(position, time)] -> [(10,1), (8,1), (0,12), (5,7), (3,3)] | Sort cars on position, cars = [(0,12), (3,3), (5,7), (8,1), (10,1)] | Loop start with the car closest to target, (10,1). | Pop: time. If current time > previous time, then this car won't catch up with | the previous car (because current is farther from target than previous, and | this farther car will take more time to reach the target). :: class Solution: def carFleet(self, target: int, position: List[int], speed: List[int]) -> int: time = [0] * len(position) cars = [0] * len(position) for i in range(len(position)): time[i] = (target - position[i]) / speed[i] cars[i] = (position[i], time[i]) cars.sort() fleet = 0 prev_time = 0 while cars: p,t = cars.pop() if t > prev_time: fleet +=1 prev_time = t #update only if found greater time return fleet | **Solution, initial** | Keys: | -sort input in reverse | -work with values in stack[-1], stack[-2]. Put only time to stack. | Steps | -make pairs (position, speed) | -sort pairs in reverse (meaning it will sort on position. Greatest..Smallest) | NOTE: Greater position means the car is Closer to target ==> 4 is closer than 2. | -loop our sorted pairs | -On stack we will be putting "Time to get to the target" | -The lookup stack[-1] and stack[-2] checks if: | the Further away car (stack[-1]) would get to target Faster than the closer car (stack[-2]). | If yes, we can join that faster car to the fleet of the slower car: stack.pop() Solution [:ref:`10 `] :: def carFleet(target: int, position: List[int], speed: List[int]) -> int: pair = [(p, s) for p, s in zip(position, speed)] pair.sort(reverse=True) stack = [] #[time1, time2..] for p, s in pair: # Reverse Sorted Order stack.append((target - p) / s) #** if len(stack) >= 2 and stack[-1] <= stack[-2]: stack.pop() return len(stack) #** - Unlike many algs where we put to stack as last step, here we first put to stack, and work with values stack[-1], stack[-2], because otherwise we don't check if to merge the last car into a fleet with previous cars. C++ ^^^^^^^^^^^^ | My V (LC accepted 18, 5) | (Making a separate vector of vectors to store [[position, time]] is space costly, but helps with the clarity.) .. code-block:: cpp class Solution { public: int carFleet(int target, vector& position, vector& speed) { vector> cars; for(size_t i = 0; i != position.size(); ++i){ double t = (target - position[i]) / static_cast(speed[i]); cars.push_back({static_cast(position[i]), t}); } sort(cars.begin(), cars.end()); int fleet = 0; double prev_time = 0; while (!cars.empty()){ double t = cars.back()[1]; cars.pop_back(); if(t > prev_time){ ++fleet; prev_time = t; } } return fleet; } }; 180. (LC 84) Largest Rectangle in Histogram ----------------------------------------------- `84. Largest Rectangle in Histogram `_ Hard | **Keys:** | -If current height is greater than previous, then previous can extend its width +1. | ->So monotonic increasing stack. | -Store in stack [index, height] | **Details:** | -After popping from stack, record the cur value not with its cur index, | but with index of the last popped greater value. :: # stk=[(1,0),(5,2),(6,3)], next v=2, after popping stk=[(1,0),(2,3)] # ^ | Because the lesser value can start (extends) from the first greater value. | -After the main alg, when cleaning out values from stack, our stack is mono increasing, | means all items in stack can extend to the last index of heights. | int index = heights.size(); | area = max(area, v * (index-ind)); **My V** (LC accepted 35, 30%) :: class Solution: def largestRectangleArea(self, heights: List[int]) -> int: ans = 0 stack = [] #[index, height] for i, h in enumerate(heights): start = i while stack and stack[-1][1] > h: index, height = stack.pop() area = height * (i-index) ans = max(ans, area) start = index stack.append([start, h]) for i, h in stack: area = h * (len(heights) - i) ans = max(ans, area) return ans **My V2** (LC accepted 30, 42%) :: class Solution: def largestRectangleArea(self, heights: List[int]) -> int: stack = [] #[(v,i),..] mono increasing area = 0 for i, n in enumerate(heights): if not stack or stack[-1][0] <= n: stack.append((n,i)) else: while stack and stack[-1][0] > n: v, ind = stack.pop() area = max(area, (i-ind)*v) stack.append((n, ind)) #append to stack with last popped index index=len(heights) while stack: v,ind = stack.pop() area = max(area, v*(index-ind)) return area **Solution 1** [:ref:`10 `] :: class Solution: def largestRectangleArea(self, heights: List[int]) -> int: maxArea = 0 stack = [] # pair: (index, height) for i, h in enumerate(heights): start = i #**1 while stack and stack[-1][1] > h: index, height = stack.pop() maxArea = max(maxArea, height * (i - index)) start = index #**1 stack.append((start, h)) for i, h in stack: #**2 maxArea = max(maxArea, h * (len(heights) - i)) return maxArea | #**1 | E.g. | heights = [5,6,2] | stack = [(1,5),(2,6)] | Looking at the height 2. | stack[-1][1]=5 > 2. We have to pop taller rects from the stack. | Pop, calculate area. | Then we record to stack not (2,2), but (0,2). | 0 being the ==> index of the last popped rect. | ==> Because rect 2 can extend backwards all the way to index 0 here. | Patterns: | - heights = [1,2] | Looking at 1, the next height is greater, means we can extend height=1 to the right. | We put (i, h) to stack | ->2 | 1 2 | - heights = [2,1] | Looking at 2, the next height is smaller, means we cannot extend height=2 to the right. | 2-> | 2 1 | Then we pop all taller rects from stack. Record area for each. | NOTE. The WIDTH = i of encountered shorter rect - index of popped rect | (See #**) | #**2 | If there was only an upward trend in rects, then the stack will be full. :: # 3 # 2 3 # 1 2 3 E.g.: Area of rect 1 = 1 * ((len=3) - index=0) = 3 C++ ^^^^^^^^^ .. code-block:: cpp //My V (LC accepted 65, 55%) class Solution { public: int largestRectangleArea(vector& heights) { stack> stk; //{{val, index},..} int area {0}; for(int i=0; i!=heights.size(); ++i){ int n = heights.at(i); if(stk.empty() || stk.top().first <= n) stk.push({n,i}); else{ int ind{0}; //have to declare not in while, otherwise stays in while locally while(!stk.empty() && stk.top().first > n){ ind = stk.top().second; int v = stk.top().first; stk.pop(); area = max(area, v * (i-ind)); } stk.push({n, ind}); } } int index = heights.size(); while(!stk.empty()){ int ind = stk.top().second, v = stk.top().first; stk.pop(); area = max(area, v * (index-ind)); } return area; } };