146. LRU Cache
题目:
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的做法来一套
- 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前购买的,否则可以打个半折。
二刷
又做到这题,已经过了很久很久了... 方法还是跟上面一样,稍微简写了一点。
- 主要思路仍然是使用HashMap和Doubly LinkedList。这个Doubly LinkedList我们可以另写一个private class Node。在Node class里面记录前面和后面两个节点,以及key和value。
- 我们要保存一个global的map,以及这个双链表的head和tail。
- Get操作:
- 当key不在HashMap中返回-1
- 当key在HashMap中,我们取得这个node,将node移动到双链表尾部,返回node.val
- Set操作:
- 当key在HashMap中,我们取得这个node,更新node.val,将node移动到双链表尾部
- 否则我们用map.size()和capacity来检查是否cache容量已满
- 假如已满,我们从map和双链表中移除首节点,再从尾部添加新节点。
- 从双链表中移除使用了head = head.next,然后新的head非空时head.prev = null。
- 当新的head为空时,说明之前的head = tail,这时我们也设tail = null。
- 否则我们直接从尾部添加新节点
- 将新节点加入到map中
- 假如已满,我们从map和双链表中移除首节点,再从尾部添加新节点。
- 我们可以另写一个moveToTail()方法来将node移动到尾部,或是从尾部添加新节点。这里需要注意的点如下
- head和tail均为空,我们直接设置head = node, tail = node并且返回
- 当map中包含node.key时,说明这不是一个新节点。我们要先处理其在双链表中的前节点和后节点,再将其加入到双链表尾部并更新tail。要注意如下:
- node = tail,我们并不用做改变,直接return
- node = head,我们更新head,并且设置新head.prev = null
- 否则node既又前节点,又有后节点,我们将node.prev和node.next连接
- 执行下一步 (将node加入到双链表尾部并且更新tail)
- 否则,这是一个新节点,我们将其加入到双链表尾部,并且更新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的更多相关文章
- leetcode 146. LRU Cache 、460. LFU Cache
LRU算法是首先淘汰最长时间未被使用的页面,而LFU是先淘汰一定时间内被访问次数最少的页面,如果存在使用频度相同的多个项目,则移除最近最少使用(Least Recently Used)的项目. LFU ...
- [LeetCode] 146. LRU Cache 最近最少使用页面置换缓存器
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- Java for LeetCode 146 LRU Cache 【HARD】
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- leetcode 146. LRU Cache ----- java
esign and implement a data structure for Least Recently Used (LRU) cache. It should support the foll ...
- leetcode@ [146] LRU Cache (TreeMap)
https://leetcode.com/problems/lru-cache/ Design and implement a data structure for Least Recently Us ...
- 【LeetCode】146. LRU Cache
LRU Cache Design and implement a data structure for Least Recently Used (LRU) cache. It should suppo ...
- 146. LRU Cache (List, HashTable)
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- [LeetCode] 146. LRU Cache 近期最少使用缓存
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
- [LC] 146. LRU Cache
Design and implement a data structure for Least Recently Used (LRU) cache. It should support the fol ...
随机推荐
- .net 中连接mysql
1. 下载mysql驱动.里面包含需要连接mysql的dll.mysql-connector-net 地址:http://dev.mysql.com/downloads/file/?id=463 ...
- partial与sorted
import functools sorted_ignore_case = functools.partial(sorted,cmp=lambda s1, s2: cmp(s1.upper(), s2 ...
- 07_XPath_01_入门
[工程截图] [person.xml] <?xml version="1.0" encoding="UTF-8"?> <students> ...
- android开发 单击按钮 实现页面间的跳转
我的MainActivity.java部分代码 public class MainActivity extends ActionBarActivity { //不要定义button类型,会出错 Vie ...
- Could not find action or result 导致 页面出现404错误
注意一下语句的写法(在login.jsp中的action) 在 struts.xml中 <action name="login" class="action.Lo ...
- 我爱工程化 之 gulp 使用(二)
上一篇 介绍了gulp的安装.环境等配置.基本使用,那么现在,我们快走进 速8,深入了解吧...... 一.各种安装.环境配置.插件安装(参考上一篇文章) 二.项目基本目录结构 三.编写 gulpf ...
- head 头标签(转发)
HTML head 头标签 paddingme | 04 Oct 2014 HTML head 头部分的标签.元素有很多,涉及到浏览器对网页的渲染,SEO 等等,而各个浏览器内核以及各个国内浏览器厂商 ...
- 没用调用flush导致的数据保存丢失
在将字符串保存到文件时,我们采有下面的写法,大部分情况下,都可以直接将数据保存到文件中, using (var fs = System.IO.File.Create(path)) { var sw = ...
- 免费使用的图表控件XML/SWF Charts 5.08
免费使用的图表控件XML/SWF Charts 5.08 http://www.pin5i.com/showtopic-26053.html 10个免费的在线统计图表工具 http://paranim ...
- Linux下GPIO驱动(一) ----一个简单的LED驱动
/******************************* * *杂项设备驱动:miscdevice *majior=10; * * *****************************/ ...