题目:

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and set.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
set(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.

链接: http://leetcode.com/problems/lru-cache/

题解:

设计LRU Cache。从题意理解,基本就是设计一个数据结构,要有capacity,get(key)和set(key)。其中每次get(key)之后,被访问的数据要被更新为最新访问,而set(key)主要就是增加或者更新key指向的数据,还要考虑一下容量。 首先想到用Queue和HashMap来存储数据,结果超时了,因为在Queen里的查找使用了O(n)。 应该用Doubly LinkedList,这样可以在O(1)的时间完成get(key)。 另外看Discuss里,Java本身就提供了LinkedHashMap这种API,虽然算是cheating但可以大大简化代码。有关Java的API还是要仔细读一读。还有就是OO Design,Design Patterns,多线程,都要开始学习了。

想一想,这种题,主要就是如何用合适数据结构来存储数据。下面是一些分析:

  • Cache hit/miss: 我们要使用一个HashMap来达到O(1)的查找效果
  • Get: 我们需要使用一个Doubly Linked List,保存一个自定义结构Node,Node包含key, value, prevNode以及nextNode,主要使用remove以及addLast。最好再使用两个Node - head和tail,用来保存表头和表尾两个节点。
    • 假如miss,返回 -1
    • 假如hit
      • 记录当前结点的value
      • 在LinkdedList中remove当前的这个Node(连接前后节点)
      • 在LinkedList尾部重新加入这个节点,并且更新这个结点的prev节点为LinkedList的前队尾,更新前队尾的next为当前节点,更新当前节点的next为null
  • Set: 跟Get很像,不过要加入一些额外的判断。
    • Cache hit: 和get的hit几乎一样,只不过要增加一个步骤,update节点的value值,其余都要做
    • Cache miss:
      • List.size() < Capacity:

        • 增加新节点到队尾
        • 更新前后连接
        • 增加新节点到map
      • List.size() = Capacity
        • 移除队首节点,更新前后连接
        • 按照List.size() < Capacity的做法来一套

主要就是基本操作都要O(1)才行。 接下来根据逻辑写代码。 写是写完了,也AC了,不过又臭又长,很多重复,还有很多不必要的头尾节点判断。二刷时还要多看discuss,以及好好refactor精简一下,也要好好看看韦斯和塞奇威克的LinkedList实现。

Time Complexity - O(1), Space Complexity- O(n)。

public class LRUCache {
private Map<Integer, Node> map;
public int capacity;
private Node head;
private Node tail;
public int size; public LRUCache(int capacity) {
this.map = new HashMap<>();
this.capacity = capacity;
this.size = 0;
} public int get(int key) {
if(!map.containsKey(key)) {
return -1;
} else {
Node node = map.get(key);
if(head.key == tail.key)
return node.val;
if(node.prev == null && node.next == null)
return node.val;
if(key == tail.key) {
return node.val;
} if(key == head.key) {
if(head.next != null) {
head = head.next;
head.prev = null;
}
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
}
tail.next = node;
node.prev = tail;
node.next = null;
tail = node;
return node.val;
}
} public void set(int key, int value) {
if(map.containsKey(key)) {
Node node = map.get(key);
node.val = value;
if(head.key == tail.key)
return;
if(node.prev == null && node.next == null)
return;
if(key == tail.key) {
return;
} if(key == head.key) {
if(head.next != null) {
head = head.next;
head.prev = null;
}
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
}
tail.next = node;
node.prev = tail;
node.next = null;
tail = node; } else { //need to add new node
if(this.size == this.capacity) {
int headKey = head.key;;
if(head.key == tail.key) {
head = null;
tail = null;
} else {
if(head.next != null) {
head = head.next;
head.prev = null;
}
}
map.remove(headKey);
size--;
} Node newNode = new Node(key, value);
map.put(key, newNode);
if(head == null || tail == null) {
head = newNode;
tail = newNode;
size++;
return;
}
tail.next = newNode;
newNode.prev = tail;
newNode.next = null;
tail = newNode;
size++;
}
} private class Node {
public Node prev;
public Node next;
public int key;
public int val; public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
}

题外话:

11/1/2015 - 这几天朋友推荐了琅琊榜看。一看就不可收拾了,下班回家就是三四集,周末也是整天看。也好,赶紧看完了好好继续做题。

11/2/2015 - Node到底是结点还是节点??? 昨天又忙了一天,5点不到就去JFK接老朋友,晚上11点半才到家,累屎了。不过拿到了很多好吃的和一些购买的资料,多谢老友。不过可惜都是在双11前购买的,否则可以打个半折。

二刷

又做到这题,已经过了很久很久了... 方法还是跟上面一样,稍微简写了一点。

  1. 主要思路仍然是使用HashMap和Doubly LinkedList。这个Doubly LinkedList我们可以另写一个private class Node。在Node class里面记录前面和后面两个节点,以及key和value。
  2. 我们要保存一个global的map,以及这个双链表的head和tail。
  3. Get操作: 
    1. 当key不在HashMap中返回-1
    2. 当key在HashMap中,我们取得这个node,将node移动到双链表尾部,返回node.val
  4. Set操作:
    1. 当key在HashMap中,我们取得这个node,更新node.val,将node移动到双链表尾部
    2. 否则我们用map.size()和capacity来检查是否cache容量已满
      1. 假如已满,我们从map和双链表中移除首节点,再从尾部添加新节点。
        1. 从双链表中移除使用了head = head.next,然后新的head非空时head.prev = null。
        2. 当新的head为空时,说明之前的head = tail,这时我们也设tail = null。
      2. 否则我们直接从尾部添加新节点
      3. 将新节点加入到map中
  5. 我们可以另写一个moveToTail()方法来将node移动到尾部,或是从尾部添加新节点。这里需要注意的点如下
    1. head和tail均为空,我们直接设置head = node, tail = node并且返回
    2. 当map中包含node.key时,说明这不是一个新节点。我们要先处理其在双链表中的前节点和后节点,再将其加入到双链表尾部并更新tail。要注意如下:
      1. node = tail,我们并不用做改变,直接return
      2. node = head,我们更新head,并且设置新head.prev = null
      3. 否则node既又前节点,又有后节点,我们将node.prev和node.next连接
      4. 执行下一步 (将node加入到双链表尾部并且更新tail)
    3. 否则,这是一个新节点,我们将其加入到双链表尾部,并且更新tail

Java:

Time Complexity - O(1), Space Complexity- O(n)。  n = capacity

public class LRUCache {
private Map<Integer, Node> map;
private Node head, tail;
private int capacity = 0; public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>();
} public int get(int key) {
if (!map.containsKey(key)) return -1;
Node node = map.get(key);
moveToTail(node);
return node.val;
} public void set(int key, int value) {
if (map.containsKey(key)) {
Node node = map.get(key);
node.val = value;
moveToTail(node);
} else {
if (map.size() == capacity) {
map.remove(head.key);
head = head.next;
if (head != null) head.prev = null;
else tail = null;
}
Node node = new Node(key, value);
moveToTail(node);
map.put(key, node);
}
} private void moveToTail(Node node) {
if (head == null && tail == null) {
head = node;
tail = node;
return;
} if (map.containsKey(node.key)) {
if (node == tail) return; // node is tail if (node == head) { // node is head;
head = head.next;
head.prev = null;
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
}
} node.prev = tail;
tail.next = node;
node.next = null;
tail = node;
} private class Node {
int key;
int val;
Node prev;
Node next; public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
}

三刷:

简化了一下对head和tail的处理,建立固定的双链表头尾head和node

class LRUCache {
private Map<Integer, Node> map;
private Node head, tail;
private int capacity = 0; public LRUCache(int capacity) {
this.capacity = capacity;
this.map = new HashMap<>(capacity);
this.head = new Node(-1, 1);
this.tail = new Node(-1, 1);
head.next = tail;
tail.prev = head;
} public int get(int key) {
if (!map.containsKey(key)) return -1;
Node node = map.get(key);
moveNodeToTail(node);
return node.val;
} public void put(int key, int value) {
if (map.containsKey(key)) {
Node node = map.get(key);
node.val = value;
moveNodeToTail(node);
} else {
if (map.size() == capacity) removeNode(head.next);
Node node = new Node(key, value);
addNodeAtTail(node);
}
} private void removeNode(Node node) {
map.remove(node.key);
node.next.prev = node.prev;
node.prev.next = node.next;
} private void addNodeAtTail(Node node) {
map.put(node.key, node);
tail.prev.next = node;
node.prev = tail.prev;
node.next = tail;
tail.prev = node;
} private void moveNodeToTail(Node node) {
removeNode(node);
addNodeAtTail(node);
} private class Node {
int key;
int val;
Node prev;
Node next; public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
}

Reference:

http://www.cnblogs.com/springfor/p/3869393.html

http://algorithmsandme.in/2014/02/least-recently-used-cache/

https://leetcode.com/discuss/26560/java-solution-with-doubly-linked-list-hash-map

https://leetcode.com/discuss/16010/o-1-java-solution

https://leetcode.com/discuss/20139/java-hashtable-double-linked-list-with-touch-of-pseudo-nodes

https://leetcode.com/discuss/13964/accepted-c-solution-296-ms

https://leetcode.com/discuss/8645/my-o-1-solution-in-java

https://leetcode.com/discuss/42891/probably-the-best-java-solution-extend-linkedhashmap

https://leetcode.com/discuss/1188/java-is-linkedhashmap-considered-cheating

https://en.wikipedia.org/wiki/Cache_algorithms

146. LRU Cache的更多相关文章

  1. leetcode 146. LRU Cache 、460. LFU Cache

    LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰一定时间内被访问次数最少的页面,如果存在使用频度相同的多个项目,则移除最近最少使用(Least Recently Used)的项目. LFU ...

  2. [LeetCode] 146. LRU Cache 最近最少使用页面置换缓存器

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  3. Java for LeetCode 146 LRU Cache 【HARD】

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  4. leetcode 146. LRU Cache ----- java

    esign and implement a data structure for Least Recently Used (LRU) cache. It should support the foll ...

  5. leetcode@ [146] LRU Cache (TreeMap)

    https://leetcode.com/problems/lru-cache/ Design and implement a data structure for Least Recently Us ...

  6. 【LeetCode】146. LRU Cache

    LRU Cache Design and implement a data structure for Least Recently Used (LRU) cache. It should suppo ...

  7. 146. LRU Cache (List, HashTable)

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  8. [LeetCode] 146. LRU Cache 近期最少使用缓存

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

  9. [LC] 146. LRU Cache

    Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...

随机推荐

  1. 如何用java实现使用电子邮件控制你的电脑

    上两天看到一篇文章,用python实现电子邮件控制电脑的有趣的小程序 python 实现微信控制电脑     python版的视频教程 但是苦于自己没接触过python于是想到能不能用java实现,于 ...

  2. C# 打印文件

    这几天做的功能用到了打印这个功能,直接在网上找了点demo,在这里做个备份. 1.直接打印DataTable using System; using System.Collections.Generi ...

  3. 使用 vmstat 监测系统性能

    在linux/unix下,vmstat是常用的系统性能监测工具.常用用法如下 vmstat 1 10 表示以1秒为间隔,做相关参数的采样,一共10次.输出范例如下: procs ----------- ...

  4. Oracle——事务(Transaction)

    事务: 事务是指作为单个逻辑工作单元执行的一组相关操作. 这些操作要求全部完成或者全部不完成. 使用事务的原因:保证数据的安全有效. 事务的四个特点:(ACID) 1.原子性(Atomic):事务中所 ...

  5. Java实战之04JavaWeb-06DBUtils

    一.DBUtils 1.DBUtils的简介 Commons DbUtils是Apache组织提供的一个对JDBC进行简单封装的开源工具类库,使用它能够简化JDBC应用程序的开发,同时也不会影响程序的 ...

  6. [Guava官方文档翻译] 2.使用和避免使用null (Using And Avoiding Null Explained)

    本文地址:http://www.cnblogs.com/hamhog/p/3536647.html "null很恶心." -Doug Lea "这是一个令我追悔莫及的错误 ...

  7. 【HeadFirst设计模式】9.迭代器与组合模式

    迭代器: 定义: 提供一种方法,顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示.(不让你知道我内部是如何聚合的) 把游走的任务放在迭代器上,而不是聚合上.这样简化了聚合的接口和实现,也让责任 ...

  8. Swift 学习笔记1

    最近在看Swift,努力在看相关的文档以及书籍,因为Swift3.0的更新,以及它开源了,所以打算写一些关于Swift的相关文章.让Swift能够更好的被我理解

  9. DBA

    一个公司的数据库系统也是由DBA 来进行管理的,它们的主要工作如下: l 安装和配置数据库,创建数据库以及帐户:l 监视数据库系统,保证数据库不宕机:l 收集系统统计和性能信息以便进行调整:l 发现性 ...

  10. 补充:学会Twitter Bootstrap不再难

    博客园的兄弟姐妹们很给力,自从这篇文章写出后,有人可能会对2.x版本升级到3.x版本的区别有些好奇和模糊.现在将官方给出的说明贴上去: 从2.x升级到3.0版本 Bootstrap 3并不向后兼容Bo ...