背景
哈希表的基础
哈希表是一种更复杂更高效的数据结构,包括HashSet和HashMap,他们的查询删除一个元素的时间复杂度都是O(1)。常用来优化时间效率。哈希表虽然高效,但不是万能,如果需要对元素排序,则用TreeMap。
哈希表的设计
- 数组: 基于数组设计哈希表,元素的哈希值对数组长度的余数作为数组下标,使得查找元素时间效率为O(1)。
- 链表:当哈希值相等时,数组下标就会产生冲突,需要把存入数组的同一位置的多个元素用链表存起来。
- 扩容:为了保证高效,当元素数目和数组长度的比值超过阈值时,我们需要对数组长度扩容。
题目
请设计实现一个最近最少使用缓存,要求如下两个操作的时间复杂度都是O(1)。
- get(key)
- put(key, value)
解题思路
本题要求我们设计一个缓存的数据结构,
- 我们得把节点访问的顺序记录起来。如果我们用HashMap, 使用get,put时,可以实现时间复杂度O(1),每次访问一个节点,我们就把节点的key作为键存入hashMap中,节点最为HashMap的值储存,同时节点加入链表的末尾,这样链表的头部节点时最近最少使用的节点,而尾节点就是最新使用的节点。
- 双向链表取代单向链表。 使得能快速删除和查询节点。
需要注意的点:
- 节点数目超出阈值时,需要淘汰最近最少使用的节点。
- 借助哨兵节点,方便更新缓存。
具体的逻辑代码如下:
代码
class LRUCache {
private int capacity = 0;
private ListNode head;
private ListNode tail;
private Map<Integer, ListNode> cache;
public LRUCache(int capacity) {
this.capacity = capacity;//阈值
//初始化缓存哈希表
cache = new HashMap<Integer, ListNode>();
//初始化双向链表, 这里哨兵节点用于快速添加尾部节点。
head = new ListNode(-1, -1);
tail = new ListNode(-1, -1);
head.next = tail;
tail.pre = head;
}
public int get(int key) {
ListNode node = cache.get(key);
if(node == null) {
return -1;
}
moveToTail(node, node.value);
return node.value;
}
public void put(int key, int value) {
if(cache.containsKey(key)) {
moveToTail(cache.get(key), value);
} else {
if(cache.size() == capacity) {
ListNode tobeDeleteNode = head.next;
deleteNode(tobeDeleteNode);
cache.remove(tobeDeleteNode.key);
}
ListNode node = new ListNode(key, value);
insertToTail(node);
cache.put(key, node);
}
}
private void deleteNode(ListNode node) {
node.pre.next = node.next;
node.next.pre = node.pre;
}
private void moveToTail(ListNode node, int value) {
//detete
deleteNode(node);
//insertToTail
node.value = value;
insertToTail(node);
}
private void insertToTail(ListNode node) {
node.pre = tail.pre;
tail.pre.next = node;
node.next = tail;
tail.pre = node;
}
class ListNode {
ListNode pre;
ListNode next;
int key;
int value;
public ListNode(int key, int value){
this.key = key;
this.value = value;
}
}
}
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache obj = new LRUCache(capacity);
* int param_1 = obj.get(key);
* obj.put(key,value);
*/
总结
本题考察哈希表的知识点,需要注意最近最少使用缓存需要记录元素访问的顺序,所以最终缓存的数据结构设计成哈希表和双向链表。