首页 > 试题广场 >

设计LFU缓存结构

[编程题]设计LFU缓存结构
  • 热度指数:23499 时间限制:C/C++ 2秒,其他语言4秒 空间限制:C/C++ 256M,其他语言512M
  • 算法知识视频讲解
一个缓存结构需要实现如下功能。
  • set(key, value):将记录(key, value)插入该结构
  • get(key):返回key对应的value值
但是缓存结构中最多放K条记录,如果新的第K+1条记录要加入,就需要根据策略删掉一条记录,然后才能把新记录加入。这个策略为:在缓存结构的K条记录中,哪一个key从进入缓存结构的时刻开始,被调用set或者get的次数最少,就删掉这个key的记录;
如果调用次数最少的key有多个,上次调用发生最早的key被删除
这就是LFU缓存替换算法。实现这个结构,K作为参数给出

数据范围:
要求:get和set的时间复杂度都是 ,空间复杂度是


若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1

对于每个操作2,返回一个答案
示例1

输入

[[1,1,1],[1,2,2],[1,3,2],[1,2,4],[1,3,5],[2,2],[1,4,4],[2,1]],3

输出

[4,-1]

说明

在执行"1 4 4"后,"1 1 1"被删除。因此第二次询问的答案为-1   

备注:

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * lfu design
 * @param operators int整型二维数组 ops
 * @param k int整型 the k
 * @return int整型一维数组
 */
function LFU(operators, k) {
    // write code here
    const cache = new Map();
    const result = [];

    function get(key) {
        if (cache.has(key)) {
            const [value, count] = cache.get(key);
            cache.delete(key);
            cache.set(key, [value, count + 1]);
            result.push(value);
            return value;
        } else {
            result.push(-1);
            return -1;
        }
    }

    function set(key, value) {
        let count = 1;
        if (cache.has(key)) {
            count += cache.get(key)[1];
            cache.delete(key);
        }
        if (cache.size >= k) {
            const firstKey = cache.keys().next().value;
            const min = {
                key: firstKey,
                count: cache.get(firstKey)[1],
            };
            cache.forEach((item, key) => {
                const count = item[1];
                if (count < min.count) {
                    min.key = key;
                    min.count = count;
                }
            });
            cache.delete(min.key);
        }
        cache.set(key, [value, count]);
    }

    operators.forEach((item) => {
        const opt = item[0];
        if (opt === 1) {
            set(item[1], item[2]);
        }
        if (opt === 2) {
            get(item[1]);
        }
    });

    return result;
}


发表于 2023-11-14 18:27:05 回复(0)
双向链表+哈希表
/**

 * 定义节点
 * @param {*} key 
 * @param {*} val 
*/
var Node = function (key, val) {
    this.key = key
    this.val = val
    this.freq = 1 // 当前节点的 key 被使用的频率
    this.pre = null // 前一个节点的指针
    this.post = null // 后一个节点的指针
}

/**
 * 定义双向链表
 */
var DoublyLinkedList = function () {
    this.head = new Node(null, null) // 头节点
    this.tail = new Node(null, null) // 尾节点
    this.head.post = this.tail // 初始化时,头节点的后一个节点为尾节点
    this.tail.pre = this.head // 初始化时,尾节点的前一个节点为头节点
}

DoublyLinkedList.prototype.removeNode = function (node) {
    // 1. 将当前节点的前一个节点的 post 指针指向当前节点的 post 指针
    node.pre.post = node.post
    // 2. 将当前节点的后一个节点的 pre 指针指向当前节点的 pre 指针
    node.post.pre = node.pre
}

DoublyLinkedList.prototype.addNode = function (node) {
    // 为了方便理解,不妨设当前只有头尾节点以及需要插入的该节点
    // 总的来说,就是分别处理该节点与头尾节点的 pre/post 指针
    // 1. 将 该节点的后一个节点 设置为 头节点的后一个节点(即尾节点)
    node.post = this.head.post
    // 2. 将 尾节点的前一个节点 设置为 该节点
    this.head.post.pre = node
    // 3. 将头节点的后一个节点设置为该节点
    this.head.post = node
    // 4. 将该节点的前一个节点设置为头节点
    node.pre = this.head
}

/**
 * 定义 LFU 类
 * @param {number} capacity
 */
var LFUCache = function (capacity) {
    this.capacity = capacity // 总的容量
    this.size = 0 // 当前已使用的容量
    this.minFreq = 0 // 最小使用频率,为删除操作服务
    this.cacheMap = new Map() // key-value map
    this.freqMap = new Map() // 频率-(key,value,频率)
};

/** 
 * @param {number} key
 * @return {number}
 */
LFUCache.prototype.get = function (key) {
    // 缓存中没有这个 key,直接返回 -1
    if (!this.cacheMap.has(key)) {
        return -1
    }
    // 获取缓存
    const node = this.cacheMap.get(key)
    // 将该节点的频率 +1
    this.incFreq(node)
    // 返回该节点的值
    return node.val
};

/** 
 * @param {number} key 
 * @param {number} value
 * @return {void}
 */
LFUCache.prototype.put = function (key, value) {
    // 若缓存容量为 0,直接返回
    if (this.capacity === 0) {
        return
    }
    // 获取缓存中 key 对应的节点
    const node = this.cacheMap.get(key)
    if (node) {
        // 若节点存在,则只需要更新该节点的值以及频率
        node.val = value
        this.incFreq(node)
    } else {
        // 如果容量已被使用完,则需要移除 最不经常使用 的节点,以空出容量
        if (this.capacity === this.size) {
            // 获取最小使用频率所对应的双向链表
            const minFreqLinkedList = this.freqMap.get(this.minFreq)
            // 将该链表的尾节点的前一个节点移除(尾节点的前一个节点才是有效节点,尾节点充当哨兵作用)
            this.cacheMap.delete(minFreqLinkedList.tail.pre.key)
            minFreqLinkedList.removeNode(minFreqLinkedList.tail.pre)
            this.size--
        }
        // 将该值封装成节点并放进 cacheMap 中
        const newNode = new Node(key, value)
        this.cacheMap.set(key, newNode)
        // 同时需要将该节点插入 freqMap 中频率最小的双向链表中
        // 获取使用频率为 1 的双向链表
        let linkedList = this.freqMap.get(1)
        // 若使用频率为 1 的双向链表是空的,则创建该链表并放进 freqMap 中
        if (!linkedList) {
            linkedList = new DoublyLinkedList()
            this.freqMap.set(1, linkedList)
        }
        // 将新节点放入双向链表中,同时更新 si***Freq
        linkedList.addNode(newNode)
        this.size++
        this.minFreq = 1
    }

    /**
     * @param {Node} node
     */
    LFUCache.prototype.incFreq = function (node) {
        // 总的来说,把该节点从旧频率对应的链表中移除,然后放进新频率对应的链表中
        // 获取该节点的使用频率
        let freq = node.freq
        // 获取该使用频率(旧频率)对应的链表
        let linkedList = this.freqMap.get(freq)
        // 将该节点从旧频率对应的链表中移除
        linkedList.removeNode(node)
        // 同时满足以下两种情况时,更新 Freq 的值
        // 1. 旧频率等于最小频率
        // 2. 该链表为空链表
        if (freq === this.minFreq && linkedList.head.post === linkedList.tail) {
            this.minFreq = freq + 1
        }
        // 增加该节点的使用频率,姑且称为 新频率
        node.freq++
        // 获取新频率对应的链表
        linkedList = this.freqMap.get(freq + 1)
        // 如果链表为空,则需要新建链表,并将其放入 freqMap
        if (!linkedList) {
            linkedList = new DoublyLinkedList()
            this.freqMap.set(freq + 1, linkedList)
        }
        // 将新频率的节点放进链表中
        linkedList.addNode(node)
    }
};
/**
 * lfu design
 * @param operators int整型二维数组 ops
 * @param k int整型 the k
 * @return int整型一维数组
 */
function LFU( operators ,  k ) {
    // write code here
    const lfu = new LFUCache(k)
    const res = []
    operators.forEach( item => {
        const [type, ...rest] = item
        if (type === 1) {
            lfu.put(...rest)
        } else {
            const getVal = lfu.get(...rest)
            res.push(getVal)
        }
    })
    return res
}
module.exports = {
    LFU : LFU
};
发表于 2022-05-22 16:20:37 回复(0)

利用两个hash就可以实现时间o(1)的操作,如果用js写的话,hash数据结构需要自己实现,如果是c++的话,有内置的数据结构,这里大致说一下思路:(推荐思路不清晰的可以看看leetcode的官方题解,写的非常明白清楚)

一个hash1记录相同操作频率的记录,记录通过双向循环链表记录,子所以使用双向循环链表,是为了实现删除第一个和最后一个链表元素的时间复杂度都为o(1)。

另外一个hash2记录key和此key在第一个hash中的链表地址。

get操作:查找hash2,如果没有则返回-1,如果有,则更新hash1中记录的频率,并返回val

set操作:查找hash2,如果没有则检查hash2是否超出了范围,如果没有超出范围则直接在hash1中添加记录,并将链表位置记录在hash1中,如果超出,则删除hash1中最低频率中最早被使用的(即链表尾的元素),然后在将元素添加到hash1中对应频率的链表头;如果有,则改变频率

function LFU( operators , k ) {
    let obj = new F(k);
    const res = [];
    for (let v of operators) {
        if (v[0] === 1) {
            obj.set(v[1], v[2]);
        }else {
            res.push(obj.get(v[1]))
        }
    }
    return res;
}
class F{
    constructor(n) {
        this.hash1 = {};
        this.hash2 = {}; 
        this.size = n;
    }
    _update(p) {
        const fre = p.frequency + 1;
        const pre = p.pre;
        const next = p.next;
        pre.next = next;
        next.pre = pre;
        if (this.hash1[fre-1].next === this.hash1[fre-1].pre) {
            delete this.hash1[fre-1];
        }
        if (!this.hash1[fre]) {
            this._init(fre);
        }
        const h1 =  this.hash1[fre], h2 = h1.next;
        h1.next = p;
        p.next = h2;
        h2.pre = p;
        p.pre = h1;
    }
    _init(fre) {
        const p1 = {next: null, pre: null};
        const p2 = {next: null, pre: null};
        p1.next = p2;
        p2.next = p1;
        p1.pre= p2;
        p2.pre = p1;
        this.hash1[fre] = p1;
    }
    get(key) {
        const p = this.hash2[key];
        if (p) {
            this._update(p);
            return p.val
        }
        return -1;
    }
    set(key, value) {
        const p = this.hash2[key];
        if (p) {
            p.val = value;
            ++(p.frequency);
            this._update(p);
        } else {
            if (Object.keys(this.hash2).length >= this.size) {
                this.del();
            }
            if (!this.hash1[1]) {
                this._init(1);
            }
            const p = {next: null, pre: null, val: value, key, frequency: 1};
            const h1 = this.hash1[1], p1 = this.hash1[1].next;
            h1.next = p;
            p.next = p1;
            p1.pre = p;
            p.pre = h1;
            this.hash2[key] = p;
        };
    }
    del() {
        const min = Object.keys(this.hash1)[0];
        const h = this.hash1[min]
        const p1 = h.pre, p2 = p1.pre.pre;
        const key = p1.pre.key;
        p2.next = p1;
        p1.pre = p2;
        if (h.next === h.pre) {
            delete this.hash1[min];
        }
        delete this.hash2[key];
    }
}
发表于 2021-12-12 20:59:27 回复(0)
class Node {
    constructor(key, value, freq) {
        this.key = key;
        this.value = value;
        this.freq = freq;
        this.prev = null;
        this.next = null;
    }
}

class DoubleList {
    constructor() {
        this.headNode = new Node();
		this.tailNode = new Node();
		this.headNode.next = this.tailNode;
		this.tailNode.prev = this.headNode;
    }
    isEmpty() {
		return this.headNode.next.next === null;
	}
    addFirst(node) {
		node.next = this.headNode.next;
		node.prev = this.headNode;
		this.headNode.next.prev = node;
		this.headNode.next = node;
	}
    remove(node) {
		node.next.prev = node.prev;
		node.prev.next = node.next;
	}
    peekLast() {
		return this.tailNode.prev;
	}
}

class LFUCache {
    constructor(k) {
        this.size = k;
        this.minFreq = 0;
        this.keyTable = new Map();
        this.freqTable = new Map();
    }
    get(key) {
        if (this.size <= 0 || !this.keyTable.has(key)) return -1;
        const node = this.keyTable.get(key);
        const { freq, value } = node;
        this.freqTable.get(freq).remove(node);
        if (this.freqTable.get(freq).isEmpty()) {
            if (this.minFreq === freq) this.minFreq++;
        }
        const newNode = new Node(key, value, freq + 1);
        const freqList = this.freqTable.get(newNode.freq);
        if (!freqList) {
            this.freqTable.set(newNode.freq, new DoubleList());
        }
        this.freqTable.get(newNode.freq).addFirst(newNode);
        this.keyTable.set(key, newNode);
        return value;
    }
    set(key, value) {
        if (this.size <= 0) return;
        if (this.keyTable.has(key)) {
            const node = this.keyTable.get(key);
            const { freq } = node;
            this.freqTable.get(freq).remove(node);
            if (this.freqTable.get(freq).isEmpty()) {
                if (this.minFreq === freq) this.minFreq++;
            }
            const newNode = new Node(key, value, freq + 1);
            const freqList = this.freqTable.get(newNode.freq);
            if (!freqList) {
                this.freqTable.set(newNode.freq, new DoubleList());
            }
            this.freqTable.get(newNode.freq).addFirst(newNode);
            this.keyTable.set(key, newNode);
        } else {
            const node = new Node(key, value, 1);
            if (this.size === this.keyTable.size) {
                const freqList = this.freqTable.get(this.minFreq);
                this.keyTable.delete(freqList.peekLast().key);
                freqList.remove(freqList.peekLast());
            }
            const freqList = this.freqTable.get(node.freq);
            if (!freqList) {
                this.freqTable.set(node.freq, new DoubleList());
            }
            this.freqTable.get(node.freq).addFirst(node);
            this.minFreq = 1;
            this.keyTable.set(key, node);
        }
    }
}

/**
 * lfu design
 * @param operators int整型二维数组 ops
 * @param k int整型 the k
 * @return int整型一维数组
 */
function LFU( operators ,  k ) {
    const res = [];
    const lfu = new LFUCache(k);
    operators.forEach(op => {
        if (op[0] === 1) lfu.set(op[1], op[2]);
        else if (op[0] === 2) res.push(lfu.get(op[1]));
    });
    return res;
}

发表于 2021-04-03 23:16:57 回复(0)

问题信息

难度:
4条回答 3724浏览

热门推荐

通过挑战的用户

查看代码