【題目記錄】一些設計題

目錄

實現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);
 */

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章