经典面试题:实现LRU页面置换算法

问题描述

146. LRU为例,题目如下:
在这里插入图片描述


思路

可以定义链表节点对应缓冲块,这样就可以用双向链表保存当前的缓存块,每次加入、更新或查找缓存块都会使对应节点移动至链表头,每次需要替换出缓存块时将链尾节点(最久未使用)移除,使用链表的好处是定位到对应节点后,再进行插入或删除操作的时间复杂度都是 O ( 1 ) O(1) O(1)的,但定位链表节点就比较慢,所有可以使用哈希表记录缓存块的键对应的链表节点,这样定位链表节点的时间复杂度就是近似 O ( 1 ) O(1) O(1)的。在操作过程我们维护哈希表和链表(链表头尾节点),这样就可以实现O(1)的平均时间复杂度的 g e t get get p u t put put方法。实现过程中可以封装两个基本的方法:1) e m p l a c e _ f r o n t emplace\_front emplace_front: 将新节点插入链表头同时更新哈希表, 2) u p d a t e update update: 更新或访问链表中的节点:先将原节点从链表中删除,再将新节点插入链表头,同时更新哈希表node。


实现代码

版本1. 自定义链表节点实现

双向链表节点类:

class Node {
public:
    int k, v;//键,值
    Node *prev, *next;//前驱节点,后继节点

    Node(int key = -1, int val = -1, Node *prev_ = nullptr, Node *next_ = nullptr) : k(key), v(val), prev(prev_), next(next_) {}
};

LRU实现:

class LRUCache {
public:
    LRUCache(int capacity) {
        n = capacity;
        cnt = 0;
        head = new Node();
        tail = head;
    }

    int get(int key) {
        if (!node.count(key))
            return -1;
        update(key);
        return node[key]->v;
    }

    void put(int key, int value) {
        if (node.count(key)) {
            update(key, value);
            return;
        }
        if (cnt++ == n) {
            cnt--;
            node.erase(tail->k);
            Node *tail0 = tail;
            tail = tail->prev;
            tail->next = nullptr;
            delete tail0;
        }
        emplace_front(key, value);
    }

private:
    int n;
    Node *head, *tail;//链表头尾节点
    unordered_map<int, Node *> node;//键到链表节点的映射
    int cnt;//节点数

    void emplace_front(int key, int value) {//将(key,value)插入链表头, 同时更新哈希表node
        Node *t = new Node(key, value, head, head->next);
        if (head == tail)//可能需要更新tail节点
            tail = t;
        if (head->next)
            head->next->prev = t;
        head->next = t;
        node[key] = t;//更新哈希表
    }

    void update(int key, int val = -1) {//更新或访问(key,val): 先将(key,val)从链表中删除, 再将新的(key,val)插入链表头, 同时更新哈希表node
        Node *x = node[key];
        if (val != -1)//更新val
            x->v = val;
        if (x == tail)
            tail = tail->prev;
        x->prev->next = x->next;
        if (x->next)
            x->next->prev = x->prev;
        emplace_front(x->k, x->v);
        delete x;
    }
};

版本2. list迭代器模拟链表节点实现

使用list同时用list的迭代器模拟链表节点,这样的好处就是不用手动维护链表的尾节点,同时list有 e m p l a c e _ f r o n t emplace\_front emplace_front p o p _ b a c k pop\_back pop_back等方法,就不用再去实现一遍,所有通过list迭代器模拟链表节点来实现比自定义链表节点实现会更简洁一些。

class LRUCache {
public:
    LRUCache(int capacity) {
        n = capacity;
    }

    int get(int key) {
        if (!node.count(key))
            return -1;
        update(key);
        return node[key]->second;
    }

    void put(int key, int value) {
        if (node.count(key)) {
            update(key, value);
            return;
        }
        if (li.size() == n) {
            node.erase(li.rbegin()->first);
            li.pop_back();
        }
        li.emplace_front(key, value);
        node[key] = li.begin();
    }

private:
    int n;
    list<pair<int, int>> li;//链表
    unordered_map<int, list<pair<int, int>>::iterator> node;//键到链表节点的映射

    void update(int key, int val = -1) {//更新或访问(key,val): 先将(key,val)从链表中删除, 再将新的(key,val)插入链表头, 同时更新哈希表node
        auto x = *node[key];
        if (val != -1)
            x.second = val;
        li.erase(node[key]);
        li.push_front(x);
        node[key] = li.begin();
    }
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值