一、题目
请你为 最不经常使用(LFU)缓存算法设计并实现数据结构。它应该支持以下操作:get 和 put。
get(key) - 如果键存在于缓存中,则获取键的值(总是正数),否则返回 -1。
put(key, value) - 如果键已存在,则变更其值;如果键不存在,请插入键值对。当缓存达到其容量时,则应该在插入新项之前,使最不经常使用的项无效。在此问题中,当存在平局(即两个或更多个键具有相同使用频率)时,应该去除 最近 最少使用的键。
「项的使用次数」就是自插入该项以来对其调用 get 和 put 函数的次数之和。使用次数会在对应项被移除后置为 0 。
二、思路
1)封装一个包含数据内容以及访问频次的节点。
2) 通过双向链表记录每个访问频次对应的节点。
3)根据添加和删除节点更新记录缓存中节点的最小访问频次。
三、实现
public class LFUCache { Map<Integer, Node> cache; Map<Integer, LinkedHashSet<Node>> freqMap; int minFreq; int capacity; public LFUCache(int capacity) { cache = new HashMap<>(capacity); freqMap = new HashMap<>(); minFreq = 1; this.capacity = capacity; } public int get(int key) { Node node = cache.get(key); if (node == null) { return -1; } updateFreq(node); return node.value; } public void put(int key, int value) { if (capacity == 0) { return; } Node node = cache.get(key); if (node == null) { if (cache.size() == capacity) { removeNode(); } Node newNode = new Node(key, value); addNode(newNode); } else { node.value = value; updateFreq(node); } } public void updateFreq(Node node) { int freq = node.freq; LinkedHashSet<Node> set = freqMap.get(freq); set.remove(node); if (freq == minFreq && set.size() == 0) { minFreq = freq + 1; } node.freq++; LinkedHashSet<Node> newSet = freqMap.get(freq + 1); if (newSet == null) { newSet = new LinkedHashSet<>(); freqMap.put(freq + 1, newSet); } newSet.add(node); } public void removeNode() { LinkedHashSet<Node> set = freqMap.get(minFreq); Node node = set.iterator().next(); set.remove(node); cache.remove(node.key); } public void addNode(Node node) { int freq = node.freq; LinkedHashSet<Node> set = freqMap.get(freq); if (set == null) { set = new LinkedHashSet<>(); freqMap.put(node.freq, set); } set.add(node); minFreq = node.freq; cache.put(node.key, node); } class Node { int key; int value; int freq; public Node(int key, int value) { this.key = key; this.value = value; freq = 1; } } }