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.
题述如上。
第一次编写的代码,思路是通过引用计数器来实现,功能通过,但又因为要不断地维护这个计数器而效率低下导致未accepted。
public class LRUCache {
class Cache {
int key;
int value;
int count;
}
Cache[] Cc;
public LRUCache(int capacity) {
Cc = new Cache[capacity];
for (int i = 0; i < Cc.length; i++) {
Cc[i] = new Cache();
Cc[i].value = -1;
Cc[i].count = i;
}
}
public int get(int key) {
for (int i = 0; i < Cc.length; i++) {
if (Cc[i].key == key) {
if (Cc[i].count != Cc.length - 1) {
Cc[i].count = Cc.length - 1;
for (int j = 0; j < Cc.length; j++) {
Cc[j].count--;
}
}
return Cc[i].value;
}
}
return -1;
}
public void set(int key, int value) {
for (int i = 0; i < Cc.length; i++) {
Cc[i].count--;
if (Cc[i].count == -1) {
Cc[i].key = key;
Cc[i].value = value;
Cc[i].count = Cc.length - 1;
}
}
}
public static void main(String[] args) {
LRUCache lru = new LRUCache(10);
lru.set(2, 2);
lru.set(3, 2);
lru.set(11, 9);
for (int i = 0; i < lru.Cc.length; i++) {
System.out.println(lru.Cc[i].key + "count" + lru.Cc[i].count
+ "value" + lru.Cc[i].value);
}
System.out.println(lru.get(11));
}
}
然后我又默默的优化了一下。思路还是一样的。但是少了很多多余的变量
class Map {
int key;
int value;
}
Map[] map;
int capacity;
public LRUCache(int capacity) {
map = new Map[capacity];
for (int i = 0; i < capacity; i++) {
map[i] = new Map();
}
this.capacity = capacity;
}
public int get(int key) {
int i = capacity - 1;
int result = -1;
Map temp = new Map();
while (i > 0) {
if (map[i].key == key) {
result = map[i].value;
temp = map[i];
while (i < capacity - 1) {
map[i] = map[i + 1];
i++;
}
map[capacity - 1] = temp;
break;
}
i--;
}
return result;
}
public void set(int key, int value) {
int i = 0;
while (i < capacity - 1) {
map[i].key = map[i + 1].key;
map[i].value = map[i + 1].value;
i++;
}
map[capacity - 1].key = key;
map[capacity - 1].value = value;
}
但是还是没有通过:
Time Limit Exceeded
然后就不淡然了。。主要的问题是如何维护一个管道 。而且还是可调配顺序的。免不了要去用循环向后压数据,那么问题就在于如何去‘压’这个数。
然后。。想到了。。指针。。然后是自然的链表,有key和value其实可以用hashmap.但是不太想用已经现有的东西。所以我决定写一个由简到繁的代码。
第一个:用java里边自带的linkedhashmap。简直就是完全为了本题而出的。
简直像cheating.
第二个就是自己写个链表。然后用hashmap的
可以看到自己写链表的话效率上会快那么 一点。主要是因为自己的功能单一。所以相当于精简了不必要的代码而提高了效率。
第三个比较有挑战性啦。那就是连hashmap都自己来实现一下
hashmap的结构特别像一个珠帘。同样下标的数据将会放在一串珠子上。
高度参考 Hashmap的源代码,然后进行部分重现,对于Java 8的hashmap有时间将继续实现,Java 8在进行hashmap的实现时,如果 hash碰撞非常多将自动转换成treemap
主要实现难点及技巧:
用途:用于之后各种的计算下标,如想找到第5个数时,5&Mapsize-1。比如说Mapsize此时是16,那么就是5&15=5,101&1111=101=5.很神奇的地方是当下标大于size时,比如说16&15将等于0,也就是达到了循环赋值的目的。
两个变量prev 和next 分别用于存储要删除结点的前一个和后一个结点,当找到要删除的结点e时,只需要将prev=next即可。
if (prev == e)是用来判断第一次循环时也就是mymap[index]的本身是不是要删除的那个结点,是的话直接等于下一个就好。
这次的运行效率自然应比上一个高了。
为了再次优化将构造函数变为
以减少hash碰撞。
然后。。想到了。。指针。。然后是自然的链表,有key和value其实可以用hashmap.但是不太想用已经现有的东西。所以我决定写一个由简到繁的代码。
第一个:用java里边自带的linkedhashmap。简直就是完全为了本题而出的。
简直像cheating.
public class LRUCache {
private int capacity;
private Map<Integer, Integer> map;
public LRUCache(int c) {
this.capacity = c;
this.map = new LinkedHashMap<Integer, Integer>(capacity, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
return size() > capacity;
}
};
}
public int get(int key) {
if (!map.containsKey(key)) {
return -1;
}
return map.get(key);
}
public void set(int key, int value) {
map.put(key, value);
}
}
第二个就是自己写个链表。然后用hashmap的
class Node {
int key;
int value;
Node next;
Node pre;
Node(int key, int value) {
this.key = key;
this.value = value;
}
}
int capacity;
HashMap<Integer, Node> map;
Node head = new Node(-1, -1);
Node tail;
int size = 0;
public LRUCache(int capacity) {
this.capacity = capacity;
map = new HashMap<Integer, LRUCache.Node>(capacity);
}
public int get(int key) {
Node temp = map.get(key);
if (temp == null) {
return -1;
} else {
removeNode(temp);
temp = new Node(key, temp.value);
addNodetoTail(temp);
map.put(key, temp);
return temp.value;
}
}
public void set(int key, int value) {
Node temp = map.get(key);
if (temp == null) {
if (size == capacity) {
removeHead();
}
size++;
} else {
removeNode(temp);
}
temp = new Node(key, value);
addNodetoTail(temp);
map.put(key, temp);
}
private void removeHead() {
// TODO Auto-generated method stub
map.remove(head.key);
if (size > 1) {
head = head.next;
head.pre = null;
} else {
head = null;
}
size--;
}
private void removeNode(Node temp) {
// TODO Auto-generated method stub
if (size > 1) {
if (temp.pre != null) {// 证明是head结点
temp.pre.next = temp.next;
} else {
head = head.next;
head.pre = null;
}
if (temp.next != null)// 证明tail结点
{
temp.next.pre = temp.pre;
} else {
tail = tail.pre;
tail.next = null;
}
} else {
tail = null;
}
}
private void addNodetoTail(Node node) {
// TODO Auto-generated method stub
if (tail == null || head == null) {
head = node;
tail = node;
head.next = tail;
tail.pre = head;
head.pre = null;
tail.next = null;
} else {
tail.next = node;
node.pre = tail;
node.next = null;
tail = node;
}
}
可以看到自己写链表的话效率上会快那么 一点。主要是因为自己的功能单一。所以相当于精简了不必要的代码而提高了效率。
第三个比较有挑战性啦。那就是连hashmap都自己来实现一下
hashmap的结构特别像一个珠帘。同样下标的数据将会放在一串珠子上。
public class LRUCache {
class Node {
int key;
int value;
Node next;
Node pre;
Node(int key, int value) {
this.key = key;
this.value = value;
}
}
class Map {
int key;
Node value;
Map next;
Map(int key, Node value) {
this.key = key;
this.value = value;
}
Map(int key, Node value, Map next) {
this.key = key;
this.value = value;
this.next = next;
}
}
Map[] mymap;
int capacity = 0;
Node head = new Node(-1, -1);
Node tail;
int size = 0;
int Mapsize = 1;
public LRUCache(int capacity) {
this.capacity = capacity;
while (Mapsize < capacity) {
Mapsize = Mapsize << 1;
}
mymap = new Map[Mapsize];
}
public int get(int key) {
int index = key & Mapsize - 1;
for (Map e = mymap[index]; e != null; e = e.next) {
if (e.key == key) {
removeNode(e.value);
Node temp = new Node(key, e.value.value);
addNodetoTail(temp);
e.value = temp;
return e.value.value;
}
}
return -1;
}
public void set(int key, int value) {
Node temp = new Node(key, value);
int index = key & Mapsize - 1;
Map e = mymap[index];
for (; e != null; e = e.next) {
if (e.key == key) {
removeNode(e.value);
e.value = temp;
addNodetoTail(temp);
return;
}
}
Map next = mymap[index];
mymap[index] = new Map(key, temp, next);
if (size == capacity) {
removeHead();
}
size++;
addNodetoTail(temp);
}
private void removeHead() {
// TODO Auto-generated method stub
int index = head.key & Mapsize - 1;
Map prev = mymap[index];
Map e = prev;
while (e != null) {
Map next = e.next;
if (e.value.key == head.key) {
if (prev == e)
mymap[index] = next;
else
prev.next = next;
}
prev = e;
e = next;
}
if (size > 1) {
head = head.next;
head.pre = null;
} else {
head = null;
}
size--;
}
private void removeNode(Node temp) {
// TODO Auto-generated method stub
if (size > 1) {
if (temp.pre != null) {// 证明是head结点
temp.pre.next = temp.next;
} else {
head = head.next;
head.pre = null;
}
if (temp.next != null)// 证明tail结点
{
temp.next.pre = temp.pre;
} else {
tail = tail.pre;
tail.next = null;
}
} else {
tail = null;
}
}
private void addNodetoTail(Node node) {
// TODO Auto-generated method stub
if (tail == null) {
head = node;
tail = node;
head.next = tail;
tail.pre = head;
head.pre = null;
tail.next = null;
} else {
tail.next = node;
node.pre = tail;
node.next = null;
tail = node;
}
}
public static void main(String[] args) {
LRUCache lru = new LRUCache(2);
lru.set(1, 1);
lru.set(2, 1);
get(lru, 4);
lru.set(4, 1);
get(lru, 1);
get(lru, 2);
//
// for (int i = 0; i < 4; i++) {
// get(lru, i);
//
// }
// // get(lru, 5);
// // get(lru, 9);
// lru.set(8, 4);
// // get(lru, 7);
// lru.set(8, 5);
// get(lru, 3);
// lru.set(80, 5);
// lru.set(10, 5);
// lru.set(11, 5);
// lru.set(1123, 5);
// lru.set(10, 5);
// get(lru, 8);
// get(lru, 10);
// get(lru, 113);
// get(lru, 11);
System.out.println("---------------------------------------------");
while (lru.head != null) {
System.out.println(lru.head.key + "value" + lru.head.value);
lru.head = lru.head.next;
}
System.out.println("---------------------------------------------"
+ lru.size);
while (lru.tail != null) {
System.out.println(lru.tail.key + "value" + lru.tail.value);
lru.tail = lru.tail.pre;
}
// [set(2,1),get(2),set(3,2),get(2),get(3)]
// Output: [1,1,-1]
// Expected: [1,-1,1]
// set(2,1),set(1,1),get(2),set(4,1),get(1),get(2)
// System.out.println(8 & 15);
}
private static void get(LRUCache lru, int key) {
System.out.println(lru.get(key) + "key=" + key);
}
}
高度参考 Hashmap的源代码,然后进行部分重现,对于Java 8的hashmap有时间将继续实现,Java 8在进行hashmap的实现时,如果 hash碰撞非常多将自动转换成treemap
主要实现难点及技巧:
int capacity = 0;
Node head = new Node(-1, -1);
Node tail;
int size = 0;
int Mapsize = 1;
public LRUCache(int capacity) {
this.capacity = capacity;
while (Mapsize < capacity) {
Mapsize = Mapsize << 1;
}
mymap = new Map[Mapsize];
}
在初始化的时候找到大于设置容量的最小的2的n次方。
用途:用于之后各种的计算下标,如想找到第5个数时,5&Mapsize-1。比如说Mapsize此时是16,那么就是5&15=5,101&1111=101=5.很神奇的地方是当下标大于size时,比如说16&15将等于0,也就是达到了循环赋值的目的。
public void set(int key, int value) {
Node temp = new Node(key, value);
int index = key & Mapsize - 1;
Map e = mymap[index];
for (; e != null; e = e.next) {
if (e.key == key) {
removeNode(e.value);
e.value = temp;
addNodetoTail(temp);
return;
}
}
Map next = mymap[index];
mymap[index] = new Map(key, temp, next);
if (size == capacity) {
removeHead();
}
size++;
addNodetoTail(temp);
}
set方法也是调试了许久,苦于java不支持指针所以要在map里加一个构造函数, Map(int key, Node value, Map next),将新加入的结点到在链表头。
private void removeHead() {
// TODO Auto-generated method stub
int index = head.key & Mapsize - 1;
Map prev = mymap[index];
Map e = prev;
while (e != null) {
Map next = e.next;
if (e.value.key == head.key) {
if (prev == e)
mymap[index] = next;
else
prev.next = next;
}
prev = e;
e = next;
}
if (size > 1) {
head = head.next;
head.pre = null;
} else {
head = null;
}
size--;
}
删除头指针,关键代码在于
Map prev = mymap[index];
Map e = prev;
while (e != null) {
Map next = e.next;
if (e.value.key == head.key) {
if (prev == e)
mymap[index] = next;
else
prev.next = next;
}
prev = e;
e = next;
}
两个变量prev 和next 分别用于存储要删除结点的前一个和后一个结点,当找到要删除的结点e时,只需要将prev=next即可。
if (prev == e)是用来判断第一次循环时也就是mymap[index]的本身是不是要删除的那个结点,是的话直接等于下一个就好。
这次的运行效率自然应比上一个高了。
为了再次优化将构造函数变为
while (Mapsize < capacity*2) {
Mapsize = Mapsize << 1;
}
以减少hash碰撞。