本篇文章根据labuladong的算法小抄汇总自己设计数据结构的常见算法,采用python3实现
LRU(Least Recently Used)算法是一种缓存淘汰机制。计算机的缓存容量有限,如果缓存满了就要删除一些内容,那么删除哪些内容呢?LRU认为最近使用过的数据是有用的,很久没用的就是无用的,内存满了就优先删掉很久没用过的数据。
题目:设计并实现一个满足LRU缓存约束的数据结构。
要求:put和get时间复杂度为O(1)
解决:哈希链表
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)
#双向链表 class DLinkedNode: def __init__(self,key=0,value=None): self.key = key self.value = value self.prev = None self.next = None class LRUCache: #以正整数作为容量初始化LRU缓存 def __init__(self,capacity): self.cache = dict() #伪头部节点;伪尾部节点 self.head = DLinkedNode() self.tail = DLinkedNode() self.head.next = self.tail self.tail.prev = self.head self.capacity = self.capacity self.size = 0 #如果关键字key存在于缓存中,则返回关键字的值,否则返回-1 def get(self,key): if key not in self.cache: return -1 node = self.cache[key] self.moveToHead(node) return node.value #如果key已经存在,则变更其value;如key不存在,则向缓存中插入key-value;如插入导致容量超限,则逐出最久未使用的关键字 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
栈:先进后出。
单调栈:每次新元素入栈后,栈内元素都保持有序(单调递增或单调递减)
单调栈只处理一种典型问题Next Greater Element。
给你一个数组nums,返回一个等长的结果数组,结果数组中对应索引存储着下一个更大元素,如果没有更大的元素,就存-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
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]
数组T存放近几天的气温,返回等长数组,计算对于每一天,至少等多少天才能等到一个更暖和的气温,如果等不到填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
常用套路:将数组长度翻倍
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
单调队列主要解决滑动窗口相关问题
题目:给定数组nums和正整数k,有一个大小为k的窗口在nums上从左到右滑动,请你输出每次窗口中k个元素的最大值。
#实现单调队列数据结构 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)): #先把前k-1个填满 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
官方解答:
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
【储存在列表里的完全二叉树】二叉堆在逻辑上是完全二叉树,只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针。
二叉堆主要操作:sink(下沉);swim(上浮)
二叉堆主要应用:堆排序;优先级队列
注意:第一个索引0空着不用
#数组第0位空着不用 #父节点索引 def parent(root): return root // 2 #左孩子索引 def left(root): return root * 2 #右孩子索引 def right(root): return root * 2 + 1
最大堆:每个节点都大于等于它的两个子节点
最小堆:每个节点都小于等于它的两个子节点
性质:插入或删除元素时,元素会自动排序。
底层原理:二叉堆的操作
主要API:insert插入一个元素,delMax删除最大元素(最小堆则为delMin删除最小元素)
对于最大堆来说,可能出现以下破坏其性质的情况:
(1)如果某个节点A比它的子节点小,那么A就不配做父节点,应该下沉
(2)如果某个节点A比它的父节点大,那么A就不应该做子节点,应该上浮
insert:把要插入的元素添加到堆底的最后,然后让其上浮到正确位置
delMax:把堆顶元素A和堆底最后的元素B对调,然后删除A,最后让B下沉到正确位置
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 #交换两个元素 def exch(self,i,j): pq[i],pq[j] = pq[j],pq[i] #比较两个元素 def less(self,i,j): return pq[i] < pq[j] #上浮 def swim(self,k): while (k > 1) and MaxPQ.less(MaxPQ.parent(k),k): MaxPQ.exch(MaxPQ.parent(k),k) k = MaxPQ.parent(k) #下沉 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 #插入一个新元素 def insert(self,e): N += 1 pq[N] = e MaxPQ.swim(N) #删除堆顶元素 def delMax(self): maxValue = pq[1] MaxPQ.exch(1,N) pq[N] = None N -= 1 maxPQ.sink(1) return maxValue
队列:先进先出
栈:先进后出
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时间复杂度为O(N),其它都为O(1)
class MyQueue: def __init__(self): self.s1 = [] self.s2 = [] #将元素x推到队列末尾 #顺序添加进s1 def push(self,x): self.s1.append(x) #返回队列开头的元素 #当s2为空时,可以把s1所有元素取出添加进s2 def peek(self): if not self.s2: while self.s1: self.s2.push(self.s1.pop()) return self.s2[-1] #从队列开头移除并返回元素 def pop(self): self.peek() return self.s2.pop() #队列是否为空 def empty(self): return (not self.s1) and (not self.s2)
最坏时间复杂度为O(N),但均摊时间复杂度为O(1),因为对于一个元素,最多只可能被搬运一次。