版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
题目描述
设计并实现最不经常使用(LFU)缓存的数据结构。它应该支持以下操作:get 和 put。
get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value) - 如果键不存在,请设置或插入值。当缓存达到其容量时,它应该在插入新项目之前,使最不经常使用的项目无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,最近最少使用的键将被去除。
进阶:
你是否可以在 O(1) 时间复杂度内执行两项操作?
样例
LFUCache cache = new LFUCache( 2 /* capacity (缓存容量) */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 去除 key 2
cache.get(2); // 返回 -1 (未找到key 2)
cache.get(3); // 返回 3
cache.put(4, 4); // 去除 key 1
cache.get(1); // 返回 -1 (未找到 key 1)
cache.get(3); // 返回 3
cache.get(4); // 返回 4
题解
- LFU属于OS中页面置换算法中的其中一种,称为最不常用算法。每次置换访问次数最少的页面,当最少访问次数的页面有多个时,选择上次访问最早的页面移除。
- 这种方法的缺点是有可能刚被调进来的页面没访问几次下次又被移除了,而对于一些初始访问次数较多,后来就不再被访问的页面有可能并不会被移除。
- 将操作次数连接成一个双端链表,每个节点(又称为桶)内部又是一个双端链表,表示该次数下访问的节点。
- 当一个元素key发生put或者get时,来到key所在的桶,把key从原来的桶移除,即将key的pre节点直接和next节点相连。
- 然后把key添加到次数+1之后的桶中,如果没有次数+1的桶就新建,并且保证与之前的桶链接到双向链表中;如果有次数+1的桶,就把key放在这个桶的头部。如果key原来所在的桶中只有key这一个记录,就删掉原来的桶,保证桶之间依然是双向链表的连接。
package advan01;
import java.util.HashMap;
class Node{
public Integer key;
public Integer value;
public Integer count;//发生get或put的次数总和
public Node pre;
public Node next;
public Node(int key,int value,int count) {
this.key=key;
this.value=value;
this.count=count;
}
}
class NodeList{
public Node head;//记录每一个桶内的头结点
public Node tail;//记录每一个桶内的尾结点
public NodeList pre;//桶与桶之间是双向链表
public NodeList next;
public NodeList(Node node) {
head=node;
tail=node;
}
//把一个新加入这个桶的节点放到头部
public void addNodeFromHead(Node newHead) {
newHead.next=head;
head.pre=newHead;
head=newHead;
}
public boolean isEmpty() {
return head==null;//判断这个桶是不是空的
}
public void deleteNode(Node node) {
if(head==tail) {
head=null;
tail=null;
}else {
if(node==head) {//如果删除的节点是头结点
head=node.next;
head.pre=null;
}else if(node==tail) {//删除的节点是尾结点
tail=node.pre;
tail.next=null;
}else {
node.pre.next=node.next;
node.next.pre=node.pre;
}
}
node.pre=null;
node.next=null;
}
}
public class LFUCache {
private int capacity;
private int size;
private HashMap<Integer,Node> records;
private HashMap<Node,NodeList> heads;//表示节点位于哪个桶中
private NodeList headList;//整个结构中位于最左的桶,访问次数最少的桶
public LFUCache(int capacity) {
this.capacity=capacity;
this.size=0;
records=new HashMap<>();
heads=new HashMap<>();
headList=null;
}
public int get(int key) {
if(!records.containsKey(key)) {
return -1;
}
Node node=records.get(key);
node.count++;
NodeList curNodeList=heads.get(node);
move(node,curNodeList);
return node.value;
}
public void put(int key, int value) {
if(capacity<=0)
return;
if(records.containsKey(key)) {
Node node=records.get(key);
node.value=value;
node.count++;
NodeList curNodeList=heads.get(node);//当前结点所在的桶
move(node,curNodeList);
}else {
if(size==capacity) {
Node node=headList.tail;
headList.deleteNode(node);
modifyHeadList(headList);
records.remove(node.key);
heads.remove(node);
size--;
}
Node node=new Node(key,value,1);
if(headList==null) {
headList=new NodeList(node);
}else {
if(headList.head.count.equals(node.count)) {
headList.addNodeFromHead(node);
}else {
NodeList newList=new NodeList(node);
newList.next=headList;
headList.pre=newList;
headList=newList;
}
}
records.put(key, node);
heads.put(node, headList);
size++;
}
}
private void move(Node node, NodeList oldNodeList) {
// TODO Auto-generated method stub
oldNodeList.deleteNode(node);
//判断旧桶中是否还有元素,如果桶中已经空了那么该桶应该删除,次数+1桶的前节点应该是旧桶的前节点;否则的话前节点为旧桶
NodeList preList=modifyHeadList(oldNodeList)?oldNodeList.pre:oldNodeList;
NodeList nextList=oldNodeList.next;
if(nextList==null) {
NodeList newList=new NodeList(node);
if(preList!=null)
preList.next=newList;
newList.pre=preList;
if(headList==null)
headList=newList;
heads.put(node, newList);
}else {
if(nextList.head.count.equals(node.count)) {
nextList.addNodeFromHead(node);
heads.put(node, nextList);
}else {//如果次数总和不相等,说明该节点不应该放入该桶中
NodeList newList=new NodeList(node);
if(preList!=null) {
preList.next=newList;
}
newList.pre=preList;
newList.next=nextList;
nextList.pre=newList;
if(headList==nextList) {
headList=newList;
}
heads.put(node, newList);
}
}
}
//判断该同删除一个元素之后是否为空,如果不为空则什么都不需要做
private boolean modifyHeadList(NodeList removeNodeList) {
if(removeNodeList.isEmpty()) {
if(headList==removeNodeList) {
headList=removeNodeList.next;
if(headList!=null)
headList.pre=null;
}else {
removeNodeList.pre.next=removeNodeList.next;
if(removeNodeList.next!=null)
removeNodeList.next.pre=removeNodeList.pre;
}
return true;
}
return false;
}
}