天天看點

Leetcode進階之路——LRU Cache C++三種解法

去年網易秋招面試的時候有一道現場手撕LRU的題,跟這道幾乎一樣,現記錄如下:

146. LRU Cache

Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.

get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.

put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

先說一下題意,總共有三個操作:

LRUCache obj = new LRUCache(capacity);
obj.put(1, 1);
obj.get(1);
           

第一個是建立大小為

capacity

cache

第二個是在

key=1

的位置放入

value=1

的值

第三個是取出

key=1

位置的

value

需要清楚的幾點:若重複在某個

key

的位置放入值,則原值會被新值覆寫,例如現在又執行

obj.put(1, 2)

,則現在位置1存放的是2,而不是原先的1

此外,題目也寫了是LRU,即最近最少使用,這是作業系統中非常常見的一道題,假設記憶體隻能容納3個頁大小,按照 7 0 1 2 0 3 0 4 的次序通路頁,則通路順序為:

Leetcode進階之路——LRU Cache C++三種解法

即一旦記憶體被放滿,則首先淘汰距目前最遠時間的頁中的值

那麼回到這道題本身,根據這個流程的了解,最容易想到的一個方法就是:

用一個數組來存儲資料,給每一個資料項标記一個通路時間戳,每次插入新資料項的時候,先把數組中存在的資料項的時間戳自增,并将新資料項的時間戳置為0并插入到數組中。

每次通路數組中的資料項的時候,将被通路的資料項的時間戳置為0。當數組空間已滿時,将時間戳最大的資料項淘汰。

這種方法實作很友善,就不寫了,很明顯,這樣做每次都要通路記憶體頁中的所有資料,複雜度太高,這道題如果用這種方法,也将逾時,這時候就想到,這個

<key, value>

的對應形式不就是

C++

map

的方法,而同時,也可以用一個向量來儲存每次讀入的

key

,對于新

key

則置頂,實作如下:

class LRUCache {
public:
    LRUCache(int capacity) {
        cap = capacity, cur = 0; //cur表示已存放的容量
    }
    
    int get(int key) {
        vector<int>::iterator it = find(v.begin(), v.end(), key); //在vector中找對應的key
        if(it == v.end()) return -1; 
        v.erase(it); 
        v.emplace_back(key); //每次get都将該key置頂
        return m[key];
    }
    
    void put(int key, int value) {
        if(m[key])  //如果原先key中就有值,則覆寫原值,同時置頂該key
        {
            m[key] = value;
            vector<int>::iterator it = find(v.begin(), v.end(), key);
            v.erase(it);
            v.emplace_back(key);
            return;
        }
                
        if(cur == cap)
        {
            int p = v.front();
            m[p] = 0;
            v.erase(v.begin());
            m[key] = value;
            v.emplace_back(key);
        }
        else
        {
            cur++;
            v.emplace_back(key);
            m[key] = value;
        }
    }
    int cap, cur;
    vector<int> v;
    map<int, int> m;
};
           

然後想,這段代碼能否再精簡呢?可以看到

v.erase(v.begin());
v.emplace_back(key);
           

這一段重複了很多次,完全可以用一個函數代替,且

vector

在頭尾的操作很不友善,

C++

有已實作的

list

雙向連結清單。此外,既然每次都要找

key

的位置,為什麼不把這個位置也用一個

map

存放起來呢?

于是有了下面這段代碼:

class LRUCache {
public:
	LRUCache(int capacity) {
		cap = capacity, cur = 0;
	}

	int get(int key) {
		if (cache.find(key) == cache.end())
			return -1;
		set(key);
		return cache[key];
	}

	void put(int key, int value) {
		set(key);
		cache[key] = value;
	}

private:
	int cap, cur;
	list<int> recent;
	unordered_map<int, int> cache;
	unordered_map<int, list<int>::iterator> pos;
	void set(int key)
	{
		if (cache.find(key) != cache.end())
		{
			list<int>::iterator it = pos[key];
			recent.erase(it);
			pos.erase(key);
		}
		else
		{
			if (cur >= cap)
			{
				int last = recent.back();
				recent.pop_back();
				cache.erase(last);
				pos.erase(last);
			}
			else
			{
				cur++;
			}
		}
		recent.emplace_front(key);
		pos[key] = recent.begin();
	}
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */
           

set

函數是将

key

置頂,由于無論是

put

還是

get

,都會使得“最近使用”的值更新,是以單獨用一個函數封裝

最後結果如下,當然這還不算最elegant最高效的,還有待學習~

Leetcode進階之路——LRU Cache C++三種解法