This article summarizes the common algorithms of designing data structure according to labuladong's algorithm sketch, which is implemented in Python 3
LRU algorithm
LRU(Least Recently Used) algorithm is a cache elimination mechanism. The computer's cache capacity is limited. If the cache is full, some contents will be deleted. What contents will be deleted? LRU believes that the recently used data is useful, and the data that has not been used for a long time is useless. When the memory is full, priority will be given to deleting the data that has not been used for a long time.
LRU caching mechanism, T146
Title: design and implement a data structure that meets LRU cache constraints.
Requirement: put and get time complexity is O(1)
Solution: hash linked list
- Use python's own hash + two-way linked list OrderedDict
class LRUCache(collections.OrderedDict): def __init__(self,capacity): super().__init__() self.capacity = capacity def get(self,key): if key not in self: return -1 self.move_to_end(key) return self[key] def put(self,key,value): if key in self: self.move_to_end(key) self[key] = value if len(self) > self.capacity: self.popitem(last=False)
- Self realization
#Bidirectional linked list class DLinkedNode: def __init__(self,key=0,value=None): self.key = key self.value = value self.prev = None self.next = None class LRUCache: #Initialize LRU cache with positive integer as capacity def __init__(self,capacity): self.cache = dict() #Pseudo header node; Pseudo tail node self.head = DLinkedNode() self.tail = DLinkedNode() self.head.next = self.tail self.tail.prev = self.head self.capacity = self.capacity self.size = 0 #If the keyword key exists in the cache, the value of the keyword is returned; otherwise, - 1 is returned def get(self,key): if key not in self.cache: return -1 node = self.cache[key] self.moveToHead(node) return node.value #If the key already exists, change its value; If the key does not exist, insert the key value into the cache; If the insertion results in capacity overrun, the longest unused keyword will be expelled def put(self,key,value): if key not in self.cache: node = DLinkedNode(key,value) self.cache[key] = value self.addToHead(node) self.size += 1 if self.size > self.capacity: removed = self.removeTail() self.cache.pop(removed.key) self.size -= 1 else: node = self.cache[key] node.value = value self.moveToHead(node) def addToHead(self,node): node.prev = self.head node.next = self.head.next self.head.next.prev = node self.head.next = node def removeNode(self,node): node.prev.next = node.next node.next.prev = node.prev def moveToHead(self,node): self.removeNode(node) self.addToHead(node) def removeTail(self): node = self.tail.prev self.removeNode(node) return node
Monotone stack structure
Stack: first in, then out.
Monotone stack: every time a new element is put into the stack, the elements in the stack remain orderly (monotonic increasing or monotonic decreasing)
Monotone stack only deals with one typical problem, Next Greater Element.
Monotone stack template
Give you an array nums and return an equal length result array. The corresponding index in the result array stores the next larger element. If there is no larger element, save - 1.
def nextGreaterElement(nums): n = len(nums) res = [0 for i in range(n)] s = [] for i in range(n-1,-1,-1): while s and s[-1] <= nums[i]: s.pop() res[i] = -1 if not s else s[-1] s.append(nums[i]) return res
Next larger element I, T496
def nextGreaterElement(nums1,nums2): res = {} stack = [] for num in reversed(nums2): while stack and num >= stack[-1]: stack.pop() res[num] = stack[-1] if stack else -1 stack.append(num) return [res[num] for num in nums1]
Daily temperature, T739
Array T stores the temperature in recent days, returns the equal length array, and calculates how many days to wait for a warmer temperature for each day. If not, fill in 0
def dailyTemperatures(T): n = len(T) res = [0 for i in range(T)] s = [] for i in range(n-1,-1,-1): while s and T[s[-1]] <= T[i]: s.pop() res[i] = 0 if not s else s[-1]-i s.append(i) return res
[circular array], next larger element II, T503
Common routine: double the length of the array
def nextGreaterElements(nums): n = len(nums) res = [0 for i in range(n)] stack = [] for i in range(2*n-1,-1,-1): while s and s[-1] <= nums[i % n]: s.pop() res[i % n] = s[-1] if s else -1 s.append(nums[i%n]) return res
Monotone queue
Monotone queue mainly solves the problems related to sliding window
Sliding window max, T239
Title: given the array nums and positive integer k, there is a window with size k sliding from left to right on nums. Please output the maximum value of k elements in each window.
#Implement monotonic queue data structure class MonotonicQueue: def __init__(self): self.q = LinkedList() def push(self,n): while q and q.getLast() < n: q.pollLast() q.addLast(n) def max_(self): rerurn q.getFirst() def pop(self,n): if n == q.getFirst: q.pollFirst() def maxSlidingWindow(nums,k): window = MonotonicQueue() res = [] for i in range(len(nums)): #Fill the first k-1 first if i < k - 1: window.push(nums[i]) else: window.push(nums[i]) res.append(window.max_()) window.pop(nums[i-k+1]) return res
Official answer:
def maxSlidingWindow(nums,k): n = len(nums) q = collections.deque() for i in range(k): while q and nums[i] >= nums[q[-1]]: q.pop() q.append(i) res = [nums[q[0]]] for i in range(k,n): while q and nums[i] >= nums[q[-1]]: q.pop() q.append(i) while q[0] <= i - k: q.popleft() res.append(nums[q[0]]) return res
Binary heap implementation priority queue
Binary reactor
[complete binary tree stored in the list] binary heap is logically a complete binary tree, but stored in an array. In the general linked list binary tree, we operate on the pointer of the node, while in the array, we take the array index as the pointer.
Main operation of binary reactor: sink; swim (floating up)
Binary heap is mainly used for heap sorting; Priority queue
Note: the first index 0 is empty
#Array bit 0 is empty #Parent node index def parent(root): return root // 2 #Left child index def left(root): return root * 2 #Right child index def right(root): return root * 2 + 1
Maximum heap: each node is greater than or equal to its two child nodes
Minimum heap: each node is less than or equal to its two child nodes
Priority queue
Nature: when an element is inserted or deleted, the elements are sorted automatically.
Underlying principle: operation of binary stack
Main API: insert inserts an element, and delMax deletes the largest element (the smallest heap is the smallest element deleted by delMin)
Realize floating swim and sinking sink
For the largest reactor, the following conditions may destroy its nature:
(1) If A node A is smaller than its child node, then A does not deserve to be the parent node and should sink
(2) If A node A is larger than its parent node, A should not be A child node and should float up
Implement delMax and insert
insert: add the element to be inserted to the bottom of the heap and float it up to the correct position
delMax: swap element A at the top of the heap with the last element B at the bottom of the heap, then delete A, and finally let B sink to the correct position
class MaxPQ: def __init__(self,N=0,pq=[]): self.N = N self.pq = pq def parent(self,i): return i // 2 def left(self,i): return i * 2 def right(self,i): return i * 2 + 1 #Swap two elements def exch(self,i,j): pq[i],pq[j] = pq[j],pq[i] #Compare two elements def less(self,i,j): return pq[i] < pq[j] #Float up def swim(self,k): while (k > 1) and MaxPQ.less(MaxPQ.parent(k),k): MaxPQ.exch(MaxPQ.parent(k),k) k = MaxPQ.parent(k) #sink def sink(self,k): while MaxPQ.left(k) <= N: older = MaxPQ.left(k) if (MaxPQ.right(k) <= N) and (MaxPQ.less(older,MaxPQ.right(k))): older = maxPQ.right(k) if maxPQ.less(older,k): break maxPQ.exch(k,older) k = older #Insert a new element def insert(self,e): N += 1 pq[N] = e MaxPQ.swim(N) #Delete heap top element def delMax(self): maxValue = pq[1] MaxPQ.exch(1,N) pq[N] = None N -= 1 maxPQ.sink(1) return maxValue
Queue implementation stack, T225
Queue: first in first out
Stack: first in, last out
class MyStack: def __init__(self): self.q = [] self.topelem = 0 def push(self,x): self.q.append(x) self.topelem = x def top(self): return self.topelem def pop(self): size = len(self.q) while size > 2 self.q.append(self.q.pop(0)) size -= 1 self.topelem = self.q[0] self.q.append(self.q.pop(0)) return self.q.pop(0) def empty(self): return not self.q
pop time complexity is O(N), others are O(1)
Stack implementation queue, T232
class MyQueue: def __init__(self): self.s1 = [] self.s2 = [] #Push element x to the end of the queue #Add to s1 in sequence def push(self,x): self.s1.append(x) #Returns the element at the beginning of the queue #When s2 is empty, all elements of s1 can be taken out and added to s2 def peek(self): if not self.s2: while self.s1: self.s2.push(self.s1.pop()) return self.s2[-1] #Removes and returns an element from the beginning of the queue def pop(self): self.peek() return self.s2.pop() #Is the queue empty def empty(self): return (not self.s1) and (not self.s2)
The worst time complexity is O(N), but the average time complexity is O(1), because an element can only be moved once at most.