数据结构设计 | 最小栈和LRU_cache
数据结构设计类的问题,在面试过程中作为考察编码者的基本功经常被问到,这类题目在leetcode里面数量不多。
最小栈
- leetcode155 Minstack
Design a stack that supports push, pop, top, and retrieving the minimum element in constant time.
push(x) – Push element x onto stack.
pop() – Removes the element on top of the stack.
top() – Get the top element.
getMin() – Retrieve the minimum element in the stack.
Example:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> Returns -3.
minStack.pop();
minStack.top(); --> Returns 0.
minStack.getMin(); --> Returns -2.
这道最小栈跟原来的栈相比就是多了一个功能,可以返回该栈的最小值。
解题思路,使用两个栈实现。
一个栈来按顺序存储 push 进来的数据,另一个用来存出现过的最小值。
class MinStack:
def __init__(self):
self.stack = []
self.min_stack = []
"""
@param: number: An integer
@return: nothing
"""
def push(self, number):
self.stack.append(number)
if len(self.min_stack) == 0:
self.min_stack.append(number)
else:
if number > self.min_stack[-1]:
self.min_stack.append(self.min_stack[-1])
else:
self.min_stack.append(number)
"""
@return: An integer
"""
def pop(self):
self.min_stack.pop()
return self.stack.pop()
"""
@return: An integer
"""
def min(self):
return self.min_stack[-1]
缓存设计
- leetcode146 LRUcache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.The cache is initialized with a positive capacity.
LRUCache cache = new LRUCache( 2 /* capacity */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
题目的意思是实现LRUCache算法。
LRUCache全名为Least Recently Used,即最近最少使用算法,是操作系统中发生缺页中断时常用的一种页面置换算法。
对于LRU算法主要实现两个操作:
- 访问数据块
- 将访问的数据块更新为最近访问,并返回访问的数据块。
- 添加数据块
- 如果Cache还有容量,将添加的数据块添加到Cache之后标记为最近访问。
- 如果Cache的容量已满,替换最久未访问的数据块为添加的数据块。
由于放入的元素是(key-value),很自然想到要用hash表(字典)。
由于要定义最久未访问的数据块,并很快地实现元素的替换操作,可以使用链表list,list头结点放最近访问的数据,list尾结点放最久未访问的数据.
set
如果新来一个set请求, 我们先去查hash表
- 如果已经存在了这个key, 那么我们需要更新其value, 然后将其在list的结点位置移动到链表首部(设为最近访问)。
- 如果不存在这个key, 那么我们需要在hash表和链表中都添加这个值, 并且如果添加之后链表长度超过最大长度, 我们需要将链表尾部的节点删除(最久未访问), 并且删除其在hash表中的记录。
get
如果新来get请求,则也先去查hash表。
- 如果已经存在了这个key, 那么我们返回value, 然后将其在list的结点位置移动到链表首部(设为最近访问)。
- 不存在key,返回-1。
另外一个非常关键的点是,在链表中查找一个节点的时间复杂度是O(n), 所以当移动一个节点到链表首部的时候,如果直接查找就是O(n);但是,如果使用hash表保存每个key对应的节点,那么就可以在O(1)的时间内移动结点到链表首部。
# 由于要删除最后一个节点,需要定义双向链表
class ListNode(object):
def __init__(self, key, val, prev_node = None, next_node = None):
self.key = key
self.val = x # node的值为key对应的value
self.prev = prev_node
self.next_node = next_node
class DoubleLinkList(object):
def __init__(self, capacity):
self.capacity = capacity
self.size = 0
self.head = None
self.tail = None
def append(self, node):
if self.size == self.capacity:
raise ValueError("The double link list has been full")
self.size += 1
if self.head is None:
self.head = self.tail = Node
return
# 在链表头部加入node
node.next = self.head
self.head.prev = node
self.head = node
def delete(self, node):
if self.size == 0:
raise ValueError("The DoubleLinkList has been empty")
self.size -= 1
if node == self.head:
temp_node = node.next
if temp_node:
temp_node.prev = None
node.next = None
self.head = temp_node
elif node == self.tail:
temp_node = node.prev
if temp_node:
temp_node.next = None
node.prev = None
self.tail = temp_node
else:
node.prev.next = node.next
node.next.prev = node.prev
return node
class LRUCache(object):
def __init__(self, capacity):
self.capacity = capacity
self.cache = dict()
self.double_list = DoubleLinkList(self.capacity)
def get(self, key):
if key not in self.cache:
return -1
cur_node = self.cache[key]
self.double_list.delete(cur_node)
self.double_list.append(cur_node)
return cur_node.val
def put(self, key, value):
if key in self.cache:
cur_node = self.cache[key]
cur_node.val = value
self.double_list.delete(cur_node)
self.double_list.append(cur_node)
else:
# 找不到key则加入
if self.double_list.size == self.capacity:
# 删除最后节点
tail_node = self.double_list.delete(self.double_list.tail)
del self.cache[tail_node.key]
# 加入新节点
new_node = ListNode(key, value)
self.double_list.append(new_node)
self.cache[key] = new_node
更多精彩文章,欢迎关注公众号“Li的白日呓语”。