【题目记录】一些设计题

目录

实现LRU

LFU

设计简化版推特


https://www.cnblogs.com/cpselvis/p/6272096.html

实现LRU

对于web开发而言,缓存必不可少,也是提高性能最常用的方式。无论是浏览器缓存(如果是chrome浏览器,可以通过chrome:://cache查看),还是服务端的缓存(通过memcached或者redis等内存数据库)。缓存不仅可以加速用户的访问,同时也可以降低服务器的负载和压力。那么,了解常见的缓存淘汰算法的策略和原理就显得特别重要。

常见的缓存算法

  • LRU (Least recently used) 最近最少使用,如果数据最近被访问过,那么将来被访问的几率也更高。
  • LFU (Least frequently used) 最不经常使用,如果一个数据在最近一段时间内使用次数很少,那么在将来一段时间内被使用的可能性也很小。
  • FIFO (Fist in first out) 先进先出, 如果一个数据最先进入缓存中,则应该最早淘汰掉。

LRU缓存

像浏览器的缓存策略、memcached的缓存策略都是使用LRU这个算法,LRU算法会将近期最不会访问的数据淘汰掉。LRU如此流行的原因是实现比较简单,而且对于实际问题也很实用,良好的运行时性能,命中率较高。下面谈谈如何实现LRU缓存:

  • 新数据插入到链表头部
  • 每当缓存命中(即缓存数据被访问),则将数据移到链表头部
  • 当链表满的时候,将链表尾部的数据丢弃

LRU Cache具备的操作:

  • set(key,value):如果key在hashmap中存在,则先重置对应的value值,然后获取对应的节点cur,将cur节点从链表删除,并移动到链表的头部;若果key在hashmap不存在,则新建一个节点,并将节点放到链表的头部。当Cache存满的时候,将链表最后一个节点删除即可。
  • get(key):如果key在hashmap中存在,则把对应的节点放到链表头部,并返回对应的value值;如果不存在,则返回-1。

 

LRU的c++实现

LRU实现采用双向链表 + Map 来进行实现。这里采用双向链表的原因是:如果采用普通的单链表,则删除节点的时候需要从表头开始遍历查找,效率为O(n),采用双向链表可以直接改变节点的前驱的指针指向进行删除达到O(1)的效率(直接改变尾部节点)。使用Map来保存节点的key、value值便于能在O(logN)的时间查找元素,对应get操作。(若用hashmap实现的话就能在O(1)时间内实现查找操作)

双链表节点的定义:

struct cacheNode{
	int key;
	int value;
	cacheNode *pre, *next;
	cacheNode(int k,int v): key(k),value(v),pre(NULL),next(NULL){}
};

构造的LRU类:

class LRU{
private:
	int size;
	cacheNode* head,*tail;            //维护当前的头结点和尾结点
	map<int,cacheNode*> mp;        //实现O(nlogn)的查找
	
public:
	LRU(int capacity){        //构造时设置容量大小
		size=capacity;
		head=NULL;
		tail=NULL;
	}
	
	void remove(cacheNode* node){
		if(node->pre !=NULL){			//不是删除头结点
			node->pre->next=node->next;
		}
		else{
			head=node->next;			//删除头结点
		}
		if(node->next!=NULL){			//不是删除尾结点
			node->next->pre=node->pre;
		}
		else{
			tail=node->pre;				//删除尾结点
		}
	}
	
	void setHead(cacheNode* node){
		node->next=head;
		node->pre=NULL;
		
		if(head !=NULL)
			head->pre=node;
		if(tail==NULL)
			tail=head;
	}
	
	int get(int key){		//给一个节点的key值,找出map中节点value值
		map<int,cacheNode*> :: iterator it=mp.find(key);
		if(it==mp.end()){
			return -1;
		}
		else{
			cacheNode* p=it->second;
			remove(p);
			setHead(p);                //有人访问了就移到头部
			return p->value;
		}
	}
	
	int set(int key, int value){
		map<int,cacheNode*>::iterator it=mp.find(key);
		if(it==mp.end()){                //链表中无此节点
			cacheNode* newnode= new cacheNode(key,value);
			if(mp.size()>=size){
				auto iter=mp.find(tail);
				remove(tail);
				mp.erase(iter);
			}
			setHead(newnode);
			mp[key]=newnode;
		}
		else{                //已存在此节点,将其移到头部
			cacheNode* node=it->second;
			remove(node);
			node->value=value;
			setHead(node);
		}
	}
};

 

LFU

最近最少使用

我自己的c++实现,用了双向链表+哈希表

这个版本的get是O(1),但是put操作是O(n)的,因为涉及到删除过期节点时从后向前遍历链表的操作

class LFUCache {
public:
    struct listnode{
        listnode* prev;
        listnode* next;
        int _count;    //访问频次、容量、当前缓存个数、节点key值
        int _cap;
        int _size;
        int _key;
        listnode(){
            prev=nullptr;
            next=nullptr;
            _count=1;
            _cap=0;
            _size=0;
            _key=-1;
        }
    };
    unordered_map<int,pair<int,listnode*>> m;    //哈希表
    listnode* head;                            //头尾节点
    listnode* tail;
    LFUCache(int capacity) {
        head = new listnode();
        head->_cap = capacity;
        head->_size = 0;
        tail = new listnode();
        tail->_cap = capacity;
        tail->_size = 0;
        head->next=tail;
        tail->prev = head;
    }
    
    int get(int key) {
        if(m.count(key)){
            m[key].second->_count++;
            listnode* tmp = m[key].second;
            move_node_to_head(tmp);        //被访问了,就放到链表头部,表示活跃
            return m[key].first;
        }
        return -1;
    }
    
    void put(int key, int value) {
        if(head->_cap <= 0)
            return;
        //更新已存在的
        if(m.count(key)){
            m[key].first = value;
            m[key].second->_count++;
            return ;
        }

        if(head->_size < head->_cap ){  //值总是正数
            insert_node(key,value);
            head->_size++;
        }
        else if(head->_size == head->_cap ){   //找到最近最少使用的删掉,再加入
            int count = INT_MAX;
            listnode* tmp = nullptr;
            listnode* run = tail->prev;
            int k = -1;
            //找到这个节点
            while(run!=head){
                if(run->_count < count){
                    count = run->_count;
                    tmp = run;
                    k = tmp->_key;
                }
                run = run->prev;
            }
            //删去这个节点
            del_node(tmp);
            tmp = nullptr;
            m.erase(k);

            //插入新节点
            insert_node(key,value);
        }
    }

    void insert_node(int key, int value){
        listnode* tmp = new listnode();
        head->next->prev = tmp;
        tmp->next = head->next;
        tmp->prev = head;
        head->next = tmp;
        tmp->_key = key;
        m[key] = make_pair(value,tmp);
    }
    void del_node(listnode* tmp){
        tmp->prev->next = tmp->next;
        tmp->next->prev = tmp->prev;
        delete tmp;
    }
    void move_node_to_head(listnode* tmp){
        int count = tmp->_count;
        tmp->prev->next = tmp->next;
        tmp->next->prev = tmp->prev;
        head->next->prev = tmp;
        tmp->next = head->next;
        head->next = tmp;
        tmp->prev = head;
    }
};

 

设计简化版推特

355. 设计推特

设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近十条推文。你的设计需要支持以下的几个功能:

  1. postTweet(userId, tweetId): 创建一条新的推文
  2. getNewsFeed(userId): 检索最近的十条推文。每个推文都必须是由此用户关注的人或者是用户自己发出的。推文必须按照时间顺序由最近的开始排序。
  3. follow(followerId, followeeId): 关注一个用户
  4. unfollow(followerId, followeeId): 取消关注一个用户

示例:

Twitter twitter = new Twitter();

// 用户1发送了一条新推文 (用户id = 1, 推文id = 5).
twitter.postTweet(1, 5);

// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文.
twitter.getNewsFeed(1);

// 用户1关注了用户2.
twitter.follow(1, 2);

// 用户2发送了一个新推文 (推文id = 6).
twitter.postTweet(2, 6);

// 用户1的获取推文应当返回一个列表,其中包含两个推文,id分别为 -> [6, 5].
// 推文id6应当在推文id5之前,因为它是在5之后发送的.
twitter.getNewsFeed(1);

// 用户1取消关注了用户2.
twitter.unfollow(1, 2);

// 用户1的获取推文应当返回一个列表,其中包含一个id为5的推文.
// 因为用户1已经不再关注用户2.
twitter.getNewsFeed(1);

自己实现的代码:

思路:

  • 对于每个用户,用数组存放其关注列表和推文列表
  • 每条推特被发送时赋予其毫秒级的时间戳
  • 用户拉取新推文时,开一个数组存放他自己的推文和其所有关注者的最新10条推文,再按时间排序取出最新10条即可,时间复杂度O(n)
  • 注意关注和取关操作中要判断操作对象是否为用户本身id

 

class Twitter {
public:
    struct twit{
        int tweet_id;
        time_t post_time;
        twit(int t_id){
            tweet_id = t_id;
            post_time = clock();     //赋予本条推文当前时间戳,若用time(NULL)只能获取秒级时间,会造成误差
        }
    };

    map<int,vector<int>> follow_relationship;
    map<int,vector<twit>> user_twit;
    /** Initialize your data structure here. */
    Twitter() {

    }
    
    /** Compose a new tweet. */
    void postTweet(int userId, int tweetId) {
        twit t = twit(tweetId);
        user_twit[userId].push_back(t);
    }
    
    /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */
    vector<int> getNewsFeed(int userId) {
        vector<twit> total_v(user_twit[userId]);
        vector<int> followee_v = follow_relationship[userId];   //获取关注列表
        for(int& u_id : followee_v){   
            int len = user_twit[u_id].size();
            for(int j = 0;len - 1 - j >= 0 && j<10;j++){
                total_v.push_back(user_twit[u_id][len - 1 - j]);
            }
        }
        sort(total_v.begin(),total_v.end(),[](twit& a, twit& b) -> bool {return a.post_time < b.post_time;});
        int len_v = total_v.size();
        vector<int> ans;
        for(int i = 0; len_v - 1 - i >= 0 && i < 10; i++){
            ans.push_back(total_v[len_v - 1 - i].tweet_id);
        }
        return ans;
    }
    
    /** Follower follows a followee. If the operation is invalid, it should be a no-op. */
    void follow(int followerId, int followeeId) {
        //先检查是否已关注了或本次操作是关注自己,若是,直接返回即可
        if(follow_relationship.count(followerId) != 0)
            for(auto iter = follow_relationship[followerId].begin();iter!=follow_relationship[followerId].end();iter++){
                if(*iter == followeeId){
                    return;
                }
        }
        if(followerId == followeeId)
            return;
        follow_relationship[followerId].push_back(followeeId);
    }
    
    /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */
    void unfollow(int followerId, int followeeId) {
        //此用户无关注列表或本次操作的用户id相同
        if(follow_relationship.count(followerId) == 0 || followerId == followeeId)
            return;
        for(auto iter = follow_relationship[followerId].begin();iter!=follow_relationship[followerId].end();iter++){
            if(*iter == followeeId){
                follow_relationship[followerId].erase(iter);
                return;
            }
        }
    }
};

/**
 * Your Twitter object will be instantiated and called as such:
 * Twitter* obj = new Twitter();
 * obj->postTweet(userId,tweetId);
 * vector<int> param_2 = obj->getNewsFeed(userId);
 * obj->follow(followerId,followeeId);
 * obj->unfollow(followerId,followeeId);
 */

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值