题解 | 设计LFU缓存结构

设计LFU缓存结构

https://www.nowcoder.com/practice/93aacb4a887b46d897b00823f30bfea1

1、解题思路

为了实现LFU(Least Frequently Used)缓存,我们维护了两个主要的数据结构:

  1. freq_mp:一个哈希表,映射频率到双向链表,链表中的每个节点包含频率、键和值。
  2. mp:另一个哈希表,直接映射键到双向链表中的节点迭代器。

此外,我们还需要跟踪当前的最小频率(min_freq)和缓存的剩余容量(size)。

  • set(key, value):如果键存在,更新其值和频率;如果不存在且缓存未满,直接插入;如果缓存已满,移除使用频率最低且最早访问的键,然后插入新键。
  • get(key):如果键存在,返回其值并更新使用频率;否则返回-1。

2、代码实现

C++
#include <ios>
#include <unordered_map>
#include <vector>
class Solution {
  public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * lfu design
     * @param operators int整型vector<vector<>> ops
     * @param k int整型 the k
     * @return int整型vector
     */
    vector<int> LFU(vector<vector<int> >& operators, int k) {
        // write code here
        vector<int> res;
        size = k;
        for (const auto& op : operators) {
            if (op[0] == 1) {
                set(op[1], op[2]);
            } else {
                res.push_back(get(op[1]));
            }
        }
        return res;
    }

  private:
    // 频率到双向链表的映射, 每个链表节点存储 [频率, 键, 值]
    unordered_map<int, list<vector<int>>> freq_mp;
    // 键到链表节点的迭代器的映射
    unordered_map<int, list<vector<int>>::iterator> mp;
    // 当前最小使用频率
    int min_freq = 0;
    // 缓存剩余容量
    int size = 0;

    // 更新节点, 更新频率或值
    void update(list<vector<int>>::iterator iter, int key, int value) {
        int freq = (*iter)[0];
        // 从原频率链表中移除该节点
        freq_mp[freq].erase(iter);
        // 如果原链表为空, 删除该频率条目
        if (freq_mp[freq].empty()) {
            freq_mp.erase(freq);
            // 更新最小频率
            if (min_freq == freq) {
                min_freq++;
            }
        }
        // 将节点插入到频率 +1 的链表头部
        freq_mp[freq + 1].push_front({freq + 1, key, value});
        mp[key] = freq_mp[freq + 1].begin();
    }

    // 设置键值对
    void set(int key, int value) {
        auto it = mp.find(key);
        if (it != mp.end()) {
            // 键存在, 更新其值和频率
            update(it->second, key, value);
        } else {
            // 键不存在
            if (size == 0) {
                // 缓存已满, 移除最少使用的键
                int old_key = freq_mp[min_freq].back()[1];
                freq_mp[min_freq].pop_back();
                if (freq_mp[min_freq].empty()) {
                    freq_mp.erase(min_freq);
                }
                mp.erase(old_key);
            } else {
                size--;
            }
            // 插入新建
            min_freq = 1;
            freq_mp[1].push_front({1, key, value});
            mp[key] = freq_mp[1].begin();
        }
    }

    // 获取键的值
    int get(int key) {
        auto it = mp.find(key);
        if (it != mp.end()) {
            auto iter = it->second;
            int value = (*iter)[2];
            update(iter, key, value);
            return value;
        }
        return -1;
    }
};

Java
import java.util.*;


public class Solution {
    // 频率到双向链表的映射,每个节点包含[频率, 键, 值]
    private Map<Integer, LinkedList<int[]>> freqMap;
    // 键到链表节点的映射
    private Map<Integer, int[]> keyMap;
    // 当前最小频率
    private int minFreq;
    // 缓存容量
    private int capacity;

    public Solution() {
        freqMap = new HashMap<>();
        keyMap = new HashMap<>();
        minFreq = 0;
    }

    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * lfu design
     * @param operators int整型二维数组 ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
    public int[] LFU (int[][] operators, int k) {
        // write code here
        capacity = k;
        List<Integer> res = new ArrayList<>();
        for (int[] op : operators) {
            if (op[0] == 1) {
                set(op[1], op[2]);
            } else {
                res.add(get(op[1]));
            }
        }
        return res.stream().mapToInt(Integer::intValue).toArray();
    }

    // 更新节点:更新频率或值
    private void update(int key, int value) {
        int[] node = keyMap.get(key);
        int freq = node[0];
        // 从原频率链表中移除该节点
        freqMap.get(freq).remove(node);
        if (freqMap.get(freq).isEmpty()) {
            freqMap.remove(freq);
            if (minFreq == freq) {
                minFreq++;
            }
        }
        // 将节点插入到频率+1的链表头部
        node = new int[] {freq + 1, key, value};
        freqMap.computeIfAbsent(freq + 1, k1 -> new LinkedList<>()).addFirst(node);
        keyMap.put(key, node);
    }

    // 设置键值对
    private void set(int key, int value) {
        if (keyMap.containsKey(key)) {
            update(key, value);
        } else {
            if (keyMap.size() >= capacity) {
                // 缓存已满,移除最少使用的键
                int[] oldNode = freqMap.get(minFreq).removeLast();
                keyMap.remove(oldNode[1]);
                if (freqMap.get(minFreq).isEmpty()) {
                    freqMap.remove(minFreq);
                }
            }
            // 插入新键
            minFreq = 1;
            int[] newNode = new int[] {1, key, value};
            freqMap.computeIfAbsent(1, k -> new LinkedList<>()).addFirst(newNode);
            keyMap.put(key, newNode);
        }
    }

    // 获取键的值
    private int get(int key) {
        if (keyMap.containsKey(key)) {
            int[] node = keyMap.get(key);
            update(key, node[2]);
            return node[2];
        }
        return -1;
    }
}

Python
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
# lfu design
# @param operators int整型二维数组 ops
# @param k int整型 the k
# @return int整型一维数组
#
from collections import defaultdict, OrderedDict


class Solution:
    def __init__(self):
        self.freq_map = defaultdict(OrderedDict)
        self.key_map = {}
        self.min_freq = 0
        self.capacity = 0

    def LFU(self, operators: List[List[int]], k: int) -> List[int]:
        # write code here
        self.capacity = k
        res = []
        for op in operators:
            if op[0] == 1:
                self.set(op[1], op[2])
            else:
                res.append(self.get(op[1]))
        return res

    def update(self, key, value):
        freq, val = self.key_map[key]
        # 从原频率链表中移除该节点
        del self.freq_map[freq][key]
        if not self.freq_map[freq]:
            del self.freq_map[freq]
            if self.min_freq == freq:
                self.min_freq += 1
        # 将节点插入到频率+1的链表头部
        new_freq = freq + 1
        self.freq_map[new_freq][key] = (new_freq, value)
        self.key_map[key] = (new_freq, value)

    def set(self, key, value):
        if key in self.key_map:
            self.update(key, value)
        else:
            if len(self.key_map) >= self.capacity:
                # 缓存已满,移除最少使用的键
                old_key, _ = self.freq_map[self.min_freq].popitem(last=False)
                del self.key_map[old_key]
                if not self.freq_map[self.min_freq]:
                    del self.freq_map[self.min_freq]
            # 插入新键
            self.min_freq = 1
            self.freq_map[1][key] = (1, value)
            self.key_map[key] = (1, value)

    def get(self, key):
        if key in self.key_map:
            freq, value = self.key_map[key]
            self.update(key, value)
            return value
        return -1

3、复杂度分析

  1. 数据结构: freq_mp/freqMap/freq_map:频率到双向链表的映射,每个链表的节点包含频率、键和值。mp/keyMap/key_map:键到链表节点的映射,用于快速查找键对应的节点。min_freq/minFreq:当前最小使用频率。size/capacity:缓存容量。
  2. 主要方法: update:更新节点的频率或值,并将其移动到更高频率的链表头部。set:插入或更新键值对,处理缓存满的情况。get:查找键的值,并更新其使用频率。
  3. 操作流程: set:键存在则更新频率和值;不存在则插入新键,缓存满时移除最少使用的键。get:键存在则返回值并更新频率;不存在则返回-1。

这种方法确保了getset操作的时间复杂度均为O(1),满足了题目要求。

全部评论

相关推荐

08-21 12:03
门头沟学院 Java
这到底要评估多久呀
投递小米集团等公司10个岗位
点赞 评论 收藏
分享
08-13 08:23
已编辑
西南财经大学 产品经理
点赞 评论 收藏
分享
06-27 12:54
已编辑
门头沟学院 Java
累了,讲讲我的大学经历吧,目前在家待业。我是一个二本院校软件工程专业。最开始选专业是觉得计算机感兴趣,所以选择了他。本人学习计算机是从大二暑假结束开始的,也就是大三开始。当时每天学习,我个人认为Java以及是我生活的一部分了,就这样持续学习了一年半,来到了大四上学期末,大概是在12月中旬,我终于找的到了一家上海中厂的实习,但我发现实习生的工作很枯燥,公司分配的活也不多,大多时间也是自己在自学。就这样我秋招末才找到实习。时间来到了3月中旬,公司说我可以转正,但是转正工资只有7000,不过很稳定,不加班,双休,因为要回学校参加答辩了,同时当时也是心高气傲,认为可以找到更好的,所以放弃了转正机会,回学校准备论文。准备论文期间就也没有投递简历。然后时间来到了5月中旬,这时春招基本也结束了,然后我开始投递简历,期间只是约到了几家下场面试。工资也只有6-7k,到现在我不知道该怎么办了。已经没有当初学习的心劲了,好累呀,但是又不知道该干什么去。在家就是打游戏,boss简历投一投。每天日重一次。26秋招都说是针对26届的人,25怎么办。我好绝望。要不要参加考公、考研、央国企这些的。有没有大佬可以帮帮我。为什么感觉别人找工作都是顺其自然的事情,我感觉自己每一步都在艰难追赶。八股文背了又忘背了又忘,我每次都花很长时间去理解他,可是现在感觉八股、项目都忘完了。真的已经没有力气再去学习了。图片是我的简历,有没有大哥可以指正一下,或者说我应该走哪条路,有点不想在找工作了。
码客明:太累了就休息一下兄弟,人生不会完蛋的
如果实习可以转正,你会不...
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务