leedcode-LRU

LRU缓存机制(leedcode-146)

题目描述:

  • 设计和实现一个 LRU(最近最少使用)缓存机制;它应该支持以下操作:获取数据 get 和写入数据 put
  • get(key):如果关键字存在于缓存中,则获取该关键字的值,否则返回 -1
  • put(key, value):如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间
  • 时间复杂度:O(1)

方法一:不借助 LinkedHashMap

分析:

  • put 和 get 的时间复杂度为 O(1),因此该数据结构必要的条件:查找快,插入快,删除快,有顺序之分
  • 数据结构:借助 HashMap 的快速查找和 DLinkList(双链表:双删除方便)的有顺序之分
  • HashMap<Integer, DNode> map:value值为 DNode
  • DLinkList cache:节点为 DNode(为什么不只存储 val:当容量满时,不仅仅要删除链表最后一个节点,还要把 map 里的删除,需要只要 key 值)
  • map.get(key) == node <==> False(new出来的)
  1. 双链表
  • 可以直接借助 LinkedList (内置函数:addFirst(), remove(node), removeLast(), size())
  • 下面代码为自己实现的双链表(实现函数:addFirst(), remove(node), removeLast(), size())
// 双向链表节点
class DNode{
	 int key, value;
	 DNode prior, next;
	 public DNode(int key, int value) {
		  this.key = key;
		  this.value = value;
	 }
}
// 双向链表方法
class DLinkList{
	 private DNode head, tail;
	 private int size;
 
	 // 初始化 首尾两个指针
	 public DLinkList() {
		  head = new DNode(0, 0);
		  tail = new DNode(0, 0);
		  head.next = tail;
		  tail.prior = head;
		  size = 0;
	 }
 
	 public void addFirst(DNode node) {
		  node.next = head.next;
		  head.next.prior = node;
		  head.next = node;
		  node.prior = head;
		  size++;
	 }
 
	 public void remove(DNode node) {
		  node.prior.next = node.next;
		  node.next.prior = node.prior;
		  size--;
	 }
 
	 public DNode removeLast() {
		  if(tail.prior == head) {
			   return null;
		  }
		  DNode last = tail.prior;
		  remove(last);
		  return last;
	 }
 
	 public int size() {
		  return size;
	 }
}
  1. LRUCache实现
public class LRUCache {

	 private HashMap<Integer, DNode> map;
	 private DLinkList cache;
	 private int capacity;
	 
	 public LRUCache(int capacity) {
		  this.capacity = capacity;
		  map = new HashMap<>();
		  cache = new DLinkList();
	 } 
	 
	 public int get(int key) {
		  if(!map.containsKey(key)) {
			   return -1;
		  }
		  int val = map.get(key).value;
		  // 更新位置
		  put(key, val);
		  return val;
	 }
	 
	 public void put(int key, int val) {
		  DNode node = new DNode(key, val);
		  if(map.containsKey(key)) {
			   // 删除旧的节点 添加新的至头部
			   cache.remove(map.get(key));
			   cache.addFirst(node);
			   // 更新 map
			   map.put(key, node);
		  }else {
			   if(cache.size() == capacity) {
				    // 删除最后一个节点
				    DNode last = cache.removeLast();
				    map.remove(last.key);
			   }
			   // 添加至头部
			   cache.addFirst(node);
			   map.put(key, node);
		  }
	 }
 }  

方法二:借助 LinkedHashMap

  1. LinkedHashMap简介
  • LinkedHashMap 存储数据是有序的,分为两种:插入顺序和访问顺序(通过 accessOrder 控制:默认false - 插入顺序;true - 访问顺序)
    // 构造函数
    public LinkedHashMap(int initialCapacity,
                         float loadFactor,
                         boolean accessOrder) {
        super(initialCapacity, loadFactor);
        this.accessOrder = accessOrder;
    }

LinkedHashMap实现了HashMap未实现的三个方法:

  • afterNodeAccess():每次获取元素的时候,都要调用afterNodeAccess(e)都要将元素移动到尾部
  • afterNodeInsertion():在添加元素后,判断是否需要删除元素
  • afterNodeRemoval():删除后连接链表
  • put():更新时同上面一样,先删后插(此处将最近最久未访问节点放置在头节点,而将新插入节点放入尾部)
  • get():同时更新位置
  • removeEldestEntry():删除
    // 如果返回 true,删除 eldest 节点
    protected boolean removeEldestEntry(Map.Entry<K,V> eldest) {
        return false;
    }
  1. 实现源码
  • 与方法一不同的是,此处将最近最久未访问节点放置在头节点,而将新插入节点放入尾部
public class _146_LRUCache extends LinkedHashMap<Integer, Integer>{

    private int capacity;
    
    public _146_LRUCache(int capacity) {
        super(capacity, 0.75F, true);
        this.capacity = capacity;
    }
    
    public int get(int key) {
        return super.getOrDefault(key, -1);
    }
    
    public void put(int key, int value) {
        super.put(key, value);
    }
    
    @Override
    protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
        // 当 size() 大于 capacity,删除 eldest
        return size() > capacity; 
    }
}        
        
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值