Leetcode146. LRU Cache

Leetcode146. LRU Cache

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.
Follow up:
Could you do both operations in O(1) time complexity?

Example:

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

思路分析

当存满的时候删除最远一次操作过的元素:用一个链表,每put一个元素,就把它加到链表尾部。如果get某个元素,就把这个元素移动到链表尾部。当存满的时候,就把链表头的元素删除。(这里加到链表头部和尾部都是可以的,具体实现基本相同)
即对于链表的操作有:定位头尾节点,插入节点,找到某个节点,移动节点到尾结点,删除头节点。

①使用一个dummyHead,也就是哨兵节点,不存数据,始终指向头结点,这样存满删除链表头元素的时间复杂度为O(1)

②同样的使用一个尾指针,这样到达链表尾部的时间复杂度为O(1)

③使用hashmap,找到某个节点的时间复杂度为O(1)

④我们知道插入节点无需知道节点的prev,但是删除节点的操作要将该节点的前驱节点的next指向该节点的next,即cur.prev->next = cur->next,所以我们必须要记录每个节点的前驱节点,所以这里使用循环链表

使用双向链表的一个好处是不需要额外信息删除一个节点,同时可以在常数时间内从头部或尾部插入删除节点。
在双向链表实现中,使用一个伪头部和伪尾部标记界限,这样在更新的时候就不需要检查是否是 null 节点。

在这里插入图片描述

定义节点类

class DLinkedNode {
    int key;
    int value;
    DLinkedNode prev;
    DLinkedNode next;
    public DLinkedNode(int key, int value) {
        this.key = key;
        this.value = value;
    }
}

构建双向链表类,注意链表操作的时间复杂度需要为O(1)。

class DoubleList {  
    private DLinkedNode head, tail; // 头尾虚节点

    public DoubleList() {//构造函数来初始化有两个节点的循环链表
        head = new DLinkedNode(0, 0);
        tail = new DLinkedNode(0, 0);
        head.next = tail;
		tail.prev = head;
		head.prev = null;
		tail.next = null;
    }
	
	//添加节点到末尾,先确定Node的前后,然后修改前后节点的指针
	public void addNode(DLinkedNode node) {
        node.prev = tail.prev;
        node.next = tail;

		tail.prev.next = node;
		tail.prev = node;
    }

	//删除链表中的节点
	public void removeNode(DLinkedNode node){
		node.next.prev = node.prev;
		node.prev.next = node.next;
}

	//将某个确定的元素移到链表尾部
	public void moveToTail(DLinkedNode node){
		removeNode(node);
		addNode(node);
}

	//删除链表第一个节点并返回该节点
	public DLinkedNode removeFirst(){
		DLinkedNode res = head.next;
		removeNode(res);
		return res;
	}
} 

定义并实现LRU类,结合双向链表与哈希表。

class LRUCache {
	private int capacity = 0;
    private HashMap<Integer, DLinkedNode> map = new HashMap<>();
    private DoubleList list = new DoubleList();

	//构造函数    
    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    
    //get方法:返回元素值并把这个元素移动到链表尾部。
    public int get(int key) {
        if (map.containsKey(key)) {
	        DLinkedNode node = map.get(key);
	        list.moveToTail(node);
	        return node.value;
        } else {
            return -1;
        }
    }
    
    //put方法
    //(一)如果key已经存在,则修改对应key的value,将节点移动到末尾。
    //(二)key不存在:新建节点,如果capacity满,则删除表头的节点,并将map中的key删除。将新节点插入到尾部,并在map中新建映射。
    public void put(int key, int value) {
		if (map.containsKey(key)) {
			DLinkedNode node = map.get(key);
			node.value = value;
            list.moveToTail(node);
        } else {
        	DLinkedNode node = new DLinkedNode(key,value);
            if (capacity == map.size()) {  //这里通过map的大小来比较元素个数
                DLinkedNode first = list.removeFirst();
                map.remove(first.key);
            }
            list.addNode(node);
            map.put(key,node);
        }
    }
}

get方法还可以这样写:

public int get(int key) {
  DLinkedNode node = map.get(key);
  if(node == null){
    return -1; // should raise exception here.
  }
  // move the accessed node to the Tail;
  this.moveToTail(node);
  return node.value;
}

Java

class DLinkedNode {
    int key;
    int value;
    DLinkedNode prev;
    DLinkedNode next;
    public DLinkedNode(int key, int value) {
        this.key = key;
        this.value = value;
    }
}

class DoubleList {  
    private DLinkedNode head, tail; // 头尾虚节点

    public DoubleList() {//构造函数来初始化有两个节点的循环链表
        head = new DLinkedNode(0, 0);
        tail = new DLinkedNode(0, 0);
        head.next = tail;
		tail.prev = head;
		head.prev = null;
		tail.next = null;
    }
	
	//添加节点到末尾,先确定Node的前后,然后修改前后节点的指针
	public void addNode(DLinkedNode node) {
        node.prev = tail.prev;
        node.next = tail;

		tail.prev.next = node;
		tail.prev = node;
    }

	//删除链表中的节点
	public void removeNode(DLinkedNode node){
		node.next.prev = node.prev;
		node.prev.next = node.next;
}

	//将某个确定的元素移到链表尾部
	public void moveToTail(DLinkedNode node){
		removeNode(node);
		addNode(node);
}

	//删除链表第一个节点并返回该节点
	public DLinkedNode removeFirst(){
		DLinkedNode res = head.next;
		removeNode(res);
		return res;
	}
} 

class LRUCache {
	private int capacity = 0;
    private HashMap<Integer, DLinkedNode> map = new HashMap<>();
    private DoubleList list = new DoubleList();

	//构造函数    
    public LRUCache(int capacity) {
        this.capacity = capacity;
    }
    
    //get方法:返回元素值并把这个元素移动到链表尾部。
    public int get(int key) {
        if (map.containsKey(key)) {
	        DLinkedNode node = map.get(key);
	        list.moveToTail(node);
	        return node.value;
        } else {
            return -1;
        }
    }
    
    //put方法
    //(一)如果key已经存在,则修改对应key的value,将节点移动到末尾。
    //(二)key不存在:新建节点,如果capacity满,则删除表头的节点,并将map中的key删除。将新节点插入到尾部,并在map中新建映射。
    public void put(int key, int value) {
		if (map.containsKey(key)) {
			DLinkedNode node = map.get(key);
			node.value = value;
            list.moveToTail(node);
        } else {
        	DLinkedNode node = new DLinkedNode(key,value);
            if (capacity == map.size()) {  //这里通过map的大小来比较元素个数
                DLinkedNode first = list.removeFirst();
                map.remove(first.key);
            }
            list.addNode(node);
            map.put(key,node);
        }
    }
}

Python

Dict + Double LinkedList

#220ms
class Node:
    def __init__(self, k, v):
        self.key = k
        self.val = v
        self.prev = None
        self.next = None

class LRUCache:
    def __init__(self, capacity):
        self.capacity = capacity
        self.dic = dict()
        self.head = Node(0, 0)
        self.tail = Node(0, 0)
        self.head.next = self.tail
        self.tail.prev = self.head

    def get(self, key):
        if key in self.dic:
            n = self.dic[key]
            self._remove(n)
            self._add(n)
            return n.val
        return -1

    def put(self, key, value):
        if key in self.dic:
            self._remove(self.dic[key])
        n = Node(key, value)
        self._add(n)
        self.dic[key] = n
        if len(self.dic) > self.capacity:
            n = self.head.next
            self._remove(n)
            del self.dic[n.key]

    def _remove(self, node):
        p = node.prev
        n = node.next
        p.next = n
        n.prev = p

    def _add(self, node):
        p = self.tail.prev
        p.next = node
        self.tail.prev = node
        node.prev = p
        node.next = self.tail

使用OrderedDict

#444ms
class LRUCache(object):
    def __init__(self, capacity):
        self.dic = collections.OrderedDict()
        self.remain = capacity

    def get(self, key):
        if key not in self.dic:
            return -1
        v = self.dic.pop(key) 
        self.dic[key] = v   # set key as the newest one
        return v

    def put(self, key, value):
        if key in self.dic:    
            self.dic.pop(key)
        else:
            if self.remain > 0:
                self.remain -= 1  
            else:  # self.dic is full
                self.dic.popitem(last=False) 
        self.dic[key] = value 

使用字典和队列

#752ms
class LRUCache(object):

    def __init__(self, capacity):
        self.deque = collections.deque([])
        self.dic = {}
        self.capacity = capacity

    def get(self, key):
        if key not in self.dic:
            return -1
        self.deque.remove(key)
        self.deque.append(key)
        return self.dic[key]

    def put(self, key, value):
        if key in self.dic:    
            self.deque.remove(key)
        elif len(self.dic) == self.capacity:
            v = self.deque.popleft()  # remove the Least Recently Used element
            self.dic.pop(v)
        self.deque.append(key)
        self.dic[key] = value  

C++

发布了82 篇原创文章 · 获赞 7 · 访问量 4993

猜你喜欢

转载自blog.csdn.net/magic_jiayu/article/details/104319741
今日推荐