Leetcode之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.


题述如上。

第一次编写的代码,思路是通过引用计数器来实现,功能通过,但又因为要不断地维护这个计数器而效率低下导致未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.
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碰撞。




























郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。