Stack and Queue Questions Part 2 ================================ 181. (LC 456) 132 Pattern ---------------------------- `456. 132 Pattern `_ Medium The intuition tells that the problem could be solved either greedy remembering min, max, mid values or using a stack. The fact is, it uses both. | **Logic:** | E.g. | nums = [3,1,4,2] | To the obvious logic of using a decreasing stack: | stack = [3], stack=[3,1], stack=[4], stack=[4,2] | We need to record the curMinValue: | stack = [[3,3]], stack=[[3,1]], stack=[[1,4]], stack=[[1,2]] | (more precisely: min value before current, i.e. excluding current) | If we know what was the min value, and the top of the stack is always the greater value, | then as soon as we meet value greater than min, but less than top of the stack, we got 132 pattern. | **Variables:** | -stack | Has values in decreasing order. | If we meet value greater, we pop stack. | -greedy var for curMinValue | Min value we met so far in a variable AND in stack: [curValue, curMinValue] **Solution 1** [:ref:`10 `] :: class Solution: def find132pattern(self, nums: List[int]) -> bool: stack = [] # pair [num, curLeftMin], mono-decreasing stack curMin = nums[0] for n in nums: while stack and n >= stack[-1][0]: stack.pop() if stack and n < stack[-1][0] and n > stack[-1][1]: return True stack.append([n, curMin]) curMin = min(n, curMin) return False 182. (LC 735) Asteroid Collision ---------------------------------- `735. Asteroid Collision `_ Medium | **Key:** | 3 cases when no collision: [-1, -2], [1, 2] AND [-1, 5] (same direction AND opposite directions.) | So opposite signs don't mean there has to be a collision. **Solution 1** [:ref:`10 `] :: class Solution: def asteroidCollision(self, asteroids: List[int]) -> List[int]: stack = [] for a in asteroids: while stack and a < 0 and stack[-1] > 0: #collision only when a is negative diff = a + stack[-1] if diff > 0: #a loses, it gets destroyed a = 0 elif diff < 0: #a wins stack.pop() else: #if a==stack[-1] a = 0 stack.pop() if a: #if a was not destroyed in prev steps, add it to stack stack.append(a) return stack **My V** (LC accepted 70, 90) :: class Solution: def asteroidCollision(self, asteroids: List[int]) -> List[int]: stk=[] for a in asteroids: if stk and stk[-1] > 0 and a < 0: #only if case [+, -] [->,<-] while stk and a and stk[-1] > 0: #again check [+,-], because stk could be [-2,+1] a=-2 stop after popping +1 if a + stk[-1] == 0: stk.pop() a = None elif abs(a) > abs(stk[-1]): stk.pop() elif abs(a) < abs(stk[-1]): a=None if a: stk.append(a) return stk | Break into cases: | 1)Direction | asteroids = [-1,1] | <-- --> no collision (also -1,-1 or 1,1 no collision) | | asteroids = [1,-1] | --> <-- | COLLISION | So there is collision <>, a<0; and left a>0. | | 2)Value | asteroids = [1,-3] | a is greater then the left one. | a wins, asteroids=[-3] (and if more on the left with lesser value, a will destroy them) | | asteroids = [3,-1] | a looses, left one stays. | asteroids = [3] 183. (LC 394) Decode String ------------------------------ `394. Decode String `_ Medium | **Keys:** | -Accounting for nested code, 'build substring from within nested chars'. | E.g. s='a11[c2[dd]]z' | Append to stack till you meet ']'. | stack = ['a', '1', '1' '[', 'c', '2', '[', 'd', 'd'] | When ]: start building substring from backwards till you meet '['. | After '[' pop all digits, that's your multiplier. | Substring * multiplier. | Append substring to stack. | -So you collect the answer into the stack. Because there also can be | s='a2[b]z' | +Edge case: encoding digit can have multiple digits. | =>All the integers in s are in the range [1, 300]. **My V** (LC accepted 93,64, same code also 20,99%) :: class Solution: def decodeString(self, s: str) -> str: stack = [] for c in s: if c != "]": if stack and c.isdigit() == True and stack[-1].isdigit() == True: stack[-1] += c #if several digits num like s='100[ab]' else: stack.append(c) else: char = "" while stack[-1] != "[": char = stack.pop() + char stack.pop() char = int(stack.pop()) * char stack.append(char) return "".join(stack) **My V2** (LC accepted 20, 50%) :: class Solution: def decodeString(self, s: str) -> str: stack = [] for c in s: if c == ']': letters = '' while stack: item = stack.pop() if item != '[': letters = item + letters else: break stack.append(letters * int(stack.pop())) else: if stack and c.isdigit() and stack[-1].isdigit(): #>1 digits case stack[-1] += c else: stack.append(c) return ''.join(stack) **Solution 1** [:ref:`10 `] :: class Solution: def decodeString(self, s: str) -> str: stack = [] for char in s: if char is not "]": stack.append(char) else: sub_str = "" while stack[-1] is not "[": sub_str = stack.pop() + sub_str stack.pop() multiplier = "" while stack and stack[-1].isdigit(): multiplier = stack.pop() + multiplier stack.append(int(multiplier) * sub_str) return "".join(stack) 184. (LC 895) Maximum Frequency Stack ------------------------------------------ `895. Maximum Frequency Stack `_ Hard **My V** (LC accepted 60, 50%) :: class FreqStack: def __init__(self): self.cnt = {} #{value: freq} self.freq = {} #{freq: [value1, value2..]} self.max_freq = 0 def push(self, val: int) -> None: self.cnt[val] = 1 + self.cnt.get(val, 0) if self.cnt[val] in self.freq: self.freq[self.cnt[val]].append(val) else: self.freq[self.cnt[val]] = [val] self.max_freq +=1 def pop(self) -> int: res = self.freq[self.max_freq].pop() self.cnt[res] -=1 #<--don't forget to decrement cnt of that value if len(self.freq[self.max_freq]) == 0: self.freq.pop(self.max_freq) self.max_freq -= 1 return res **Solution 1** [:ref:`10 `] :: class FreqStack: def __init__(self): self.cnt = {} self.maxCnt = 0 self.stacks = {} def push(self, val: int) -> None: valCnt = 1 + self.cnt.get(val, 0) self.cnt[val] = valCnt if valCnt > self.maxCnt: self.maxCnt = valCnt self.stacks[valCnt] = [] self.stacks[valCnt].append(val) def pop(self) -> int: res = self.stacks[self.maxCnt].pop() self.cnt[res] -= 1 if not self.stacks[self.maxCnt]: self.maxCnt -= 1 return res | **Keys** | -Internal data structures | self.cnt = {} | To keep track of the count for Each value. E.g.: | cnt = {5:3, 4:2, 3:2, 2:1} | self.maxCnt = 0 | Keep track of the max count Overall. | E.g. 3 here. | self.stacks = {} | Making groups of how many of each value currently. | obj.push(5) | self.stacks = { | 1 : [5] | } | obj.push(4) | self.stacks = { | 1 : [5,4] | } | obj.push(5) | self.stacks = { | 1 : [5,4], | 2 : [5] | } NOTE: each new value gets added to the end of the list of the group. So we can pop from a group and it will be the most recent value (relative to other items in the group). ...After pushing all values:: self.stacks = { 1 : [5,4,3,2], 2 : [5,4,3], 3 : [5] } | -Pushing | Just push from the group with the highest key. 185. (LC 901) Online Stock Span --------------------------------- `901. Online Stock Span `_ Medium | **Keys** | - Stack, store 2 values [price, span] | - With each new price pop lesser values from stack **Solution 1** [:ref:`10 `] :: class StockSpanner: def __init__(self): self.stack = [] # pair: (price, span) def next(self, price: int) -> int: span = 1 while self.stack and self.stack[-1][0] <= price: span += self.stack[-1][1] self.stack.pop() self.stack.append((price, span)) return span **My V** (LC accepted 98, 95) :: class StockSpanner: def __init__(self): self.stack = [] #[[price, span], ..] def next(self, price: int) -> int: span = 1 while self.stack and self.stack[-1][0] <= price: span += self.stack[-1][1] self.stack.pop() self.stack.append([price, span]) return span | Monotonic decreasing stack | -Compute the span for each price as you are given that price. | -Store as (price, span) pair in a stack. | -Recognize that you can compare current price with previous. If cur>prev, | lookup the span of prev, you can ignore span number of prices after that. | And keep doing this cur>prev check till you meet prev>cur. | prices = [100,80,60,70,60,75,85] | stack = [(100,1),(80,1),(60,1),(70,2),(60,1),(75,4),(85,6)] | -More so, if cur>prev, pop prev from stack | Cur price=70 :: # prices = [100,80,60,70 ..] # ^ | 70>60, pop (60,1) from stack. Before popping, set span of 70 = 1 + 2 (default + span of 60). | Append 70 and its span. | stack = [(100,1),(80,1),>(60,1)<,(70,2)] We pop because having [60, 70], we will never get past the bigger value of 70. 186. Implement queue -------------------------- Implement the basic queue API: enqueue, dequeue. | **deque and popleft()** | We will use Python collections.deque | (doubly linked list) :: import collections class Queue: def __init__(self): self._data = collections.deque() def enqueue(self, v): self._data.append(v) def dequeue(self): return self._data.popleft() 187. (LC 102) Binary Tree Level Order Traversal -------------------------------------------------- `102. Binary Tree Level Order Traversal `_ Medium **My V** (LC accepted 70, 50) :: class Solution: def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: q = collections.deque() res = [] if root: q.append(root) while q: res.append([]) for i in range(len(q)): node = q.popleft() if node.left: q.append(node.left) if node.right: q.append(node.right) res[-1].append(node.val) return res | **Solution 1** [:ref:`2 `] | -List comprehensions | Here we empty the queue each time. To be precise, we REPLACE it with children. | We don't use queue directly. We manage the task of getting the values in order by using list comprehensions. | (LC accepted 70,90%) :: def binary_tree_depth_order(tree): # tree=root res = [] if not tree: return res curr_depth_nodes = [tree] # initially one value, [3] while curr_depth_nodes: res.append( [curr.val for curr in curr_depth_nodes] ) # appends from front to end of queue curr_depth_nodes = [ #REPLACE curr values with their children child for curr in curr_depth_nodes for child in (curr.left, curr.right) if child ] return res | **Solution 2** [:ref:`10 `] | -collections.deque | (Here we don't empty the queue each level, we classically use queue/deque: | leftpop() values, and append() children.) :: class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: res = [] q = collections.deque() if root: q.append(root) while q: val = [] for i in range(len(q)): node = q.popleft() val.append(node.val) if node.left: q.append(node.left) if node.right: q.append(node.right) res.append(val) return res | **BFS** | -We use queue 'q' using collections.deque :: # q = [pop, add ] # ^ --> adding to the end # popping from front | -And sublist where we collect values for one tree level. | -How we access children, just node.left, node.right | **Procedure** | root = [3,9,20,null,null,15,7] :: # 3 # 9 20 # 15 7 | Put tree root to q. | q=[3] | popleft to sublist. We define when to stop by the initial len(q). Append sublist to ans. | -As we pop from q, we add to q the children of current values. <=== | [9, 20] | sublist=[3] 188. (LC 622) Design Circular Queue ---------------------------------------- `622. Design Circular Queue `_ Medium | **Task understanding:** | You wouldn't overwrite when q is full. | q=[1,2,3] enqueue(4) Does not overwrite 1. Return False. Wait for the dequeue() operation | before being able to enqueue. | **Keys (array):** | -Array. One pointer front. Capacity(possible), size(actual so far). | We don't remove values, just move the front pointer. | (Get everything else: rear, where to enQueue using front pointer, size, % capacity) | rear = q[(front + size-1) % capacity] | enqueue_index = (self.front + self.size) % self.capacity | **Ways to implement:** | - using an array | - using a linked list | -doubly linked | -singly linked | Probable issue with linked list implementation. | We keep creating new nodes. | But when using array, we just overwrite the same spot at index. ARRAY :: class MyCircularQueue: def __init__(self, k: int): self.q = [0] * k self.front = 0 self.size = 0 self.capacity = k def enQueue(self, value: int) -> bool: if self.isFull(): return False idx = (self.front + self.size) % self.capacity self.q[idx] = value self.size += 1 return True def deQueue(self) -> bool: if self.isEmpty(): return False self.front = (self.front + 1) % self.capacity self.size -= 1 return True def Front(self) -> int: return -1 if self.isEmpty() else self.q[self.front] def Rear(self) -> int: if self.isEmpty(): return -1 idx = (self.front + self.size - 1) % self.capacity return self.q[idx] def isEmpty(self) -> bool: return self.size == 0 def isFull(self) -> bool: return self.size == self.capacity | **Details:** | - we initiate array of size capacity. | Capacity=3 | q = [0,0,0] | - enQueue(1), enQueue(2), enQueue(3) | q = [1,2,3] | front stays 0. | - deQueue() | We don't remove the actual value. | We move front+=1 | Decrement size-=1 | Return True (operation successful). | - enQueue(4) | q = [4,2,3] | How we get the index | ``idx = (self.front + self.size) % self.capacity`` | Basically, index always = front. It's just that to prevent moving beyond q size | we perform %capacity, and move in a circle. | Move front+=1 each time we deQueue(). Means 1 space has been freed up. 189. (LC 232) Implement Queue using Stacks ---------------------------------------------- `232. Implement Queue using Stacks `_ Easy O(m) because each elem is pushed no more than twice. :: # State # push 1,2,3 # stk1 [1,2,3] # stk2 [] # pop # stk1 [] [] # stk2 [3,2,1] [3,2] # ->1 # push 4,5 # stk1 [4,5] # stk2 [3,2] **Solution 1** [:ref:`7 `] :: class MyQueue: def __init__(self): self.stk1 = [] self.stk2 = [] def push(self, x: int) -> None: self.stk1.append(x) def pop(self) -> int: self.move() return self.stk2.pop() def peek(self) -> int: self.move() return self.stk2[-1] def empty(self) -> bool: return not self.stk1 and not self.stk2 def move(self): if not self.stk2: while self.stk1: self.stk2.append(self.stk1.pop()) 190. Implement a Queue with max API --------------------------------------- | **Key:** | When max will never return an element, regardless of future updates. | Two deques. | Each dequeue operation O(n). | A single enqueue may need several operations. | Amortized T complexity of n enqueues, dequeues is O(n). :: class QueueWithMax: def __init__(self): self.q = collections.deque() self.qmax = collections.deque() def enqueue(self, x): self.q.append(x) while self.qmax and self.qmax[-1] < x: self.qmax.pop() self.qmax.append(x) def dequeue(self): if self.q: res = self.q.popleft() if res == self.qmax[0]: #leftpop() from maxq only if == self.qmax.popleft() return res raise IndexError('empty queue') def return_max(self): if self.qmax: return self.qmax[0] raise IndexError('empty queue') | **Trace table** | (We pop always from left) :: # enq(1) enq(2) enq(3) deq() enq(4) # q [1] [1,3] [1,3,6] [3,6] [3,6,4] # qmax [1] [X,3]* [X,X,6] [6]** [6] # * if cur value is greater, pop from qmax # ** if value we dequeue is smaller, don't touch qmax | My note: | but what if values are not unique? | [1,6,3,6] | qmax=[X,6,6] #We should append another 6. **My V** using 2 stacks :: class Q: def __init__(self): self.stack1 = [] self.stack2 = [] self.maxes = [] def enqueue(self, data): self.stack1.append(data) def dequeue(self): if self.is_empty(): return None if not self.stack2: self.move() value = self.stack2.pop() if value == self.maxes[-1]: self.maxes.pop() return value def return_max(self): if self.is_empty(): return None if not self.maxes: self.move() return self.maxes[-1] def move(self): while self.stack1: value = self.stack1.pop() if not self.maxes or self.maxes[-1] <= value: self.maxes.append(value) self.stack2.append(value) def is_empty(self): if not self.stack1 and not self.stack2: return True return False q = Q() q.enqueue(1) q.enqueue(3) q.enqueue(5) q.enqueue(2) q.enqueue(5) print(q.stack1) print(q.return_max()) print(q.dequeue()) print(q.dequeue()) print(q.dequeue()) print(q.return_max()) # [1, 3, 5, 2, 5] # 5 #max # 1 # 3 # 5 # 5 #max