Tag Archives: LRU

LeetCode First Unique Number

LeetCode First Unique Number

You have a queue of integers, you need to retrieve the first unique integer in the queue.

Implement the FirstUnique class:

  • FirstUnique(int[] nums) Initializes the object with the numbers in the queue.
  • int showFirstUnique() returns the value of the first unique integer of the queue, and returns -1 if there is no such integer.
  • void add(int value) insert value to the queue.

Example 1:

Input: 
["FirstUnique","showFirstUnique","add","showFirstUnique","add","showFirstUnique","add","showFirstUnique"]
[[[2,3,5]],[],[5],[],[2],[],[3],[]]
Output: 

[null,2,null,2,null,3,null,-1]

Explanation: FirstUnique firstUnique = new FirstUnique([2,3,5]); firstUnique.showFirstUnique(); // return 2 firstUnique.add(5); // the queue is now [2,3,5,5] firstUnique.showFirstUnique(); // return 2 firstUnique.add(2);            // the queue is now [2,3,5,5,2] firstUnique.showFirstUnique(); // return 3 firstUnique.add(3);            // the queue is now [2,3,5,5,2,3] firstUnique.showFirstUnique(); // return -1

Example 2:

Input: 
["FirstUnique","showFirstUnique","add","add","add","add","add","showFirstUnique"]
[[[7,7,7,7,7,7]],[],[7],[3],[3],[7],[17],[]]
Output: 

[null,-1,null,null,null,null,null,17]

Explanation: FirstUnique firstUnique = new FirstUnique([7,7,7,7,7,7]); firstUnique.showFirstUnique(); // return -1 firstUnique.add(7); // the queue is now [7,7,7,7,7,7,7] firstUnique.add(3);            // the queue is now [7,7,7,7,7,7,7,3] firstUnique.add(3);            // the queue is now [7,7,7,7,7,7,7,3,3] firstUnique.add(7);            // the queue is now [7,7,7,7,7,7,7,3,3,7] firstUnique.add(17);           // the queue is now [7,7,7,7,7,7,7,3,3,7,17] firstUnique.showFirstUnique(); // return 17

Example 3:

Input: 
["FirstUnique","showFirstUnique","add","showFirstUnique"]
[[[809]],[],[809],[]]
Output: 

[null,809,null,-1]

Explanation: FirstUnique firstUnique = new FirstUnique([809]); firstUnique.showFirstUnique(); // return 809 firstUnique.add(809); // the queue is now [809,809] firstUnique.showFirstUnique(); // return -1

Constraints:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^8
  • 1 <= value <= 10^8
  • At most 50000 calls will be made to showFirstUnique and add.

设计题。设计一个队列,能够快速返回当前队列中第一个unique的数。

和之前的LRU很类似,使用list+unordered_map实现。list保存当前unique的数,unordered_map保存每个unique的数在list中的迭代器。当插入一个数到队列中时,首先看看在不在unordered_map中,如果不在,说明这个数是unique的,插入list和unordered_map。如果在,则看看unordered_map中的迭代器,如果迭代器不为空,说明这个数之前是unique的,需要从list中删掉;否则说明这个数之前就已经不是unique的了,不做任何操作。

完整代码如下:

class FirstUnique {
private:
	list<int> uniques_;
	unordered_map<int, list<int>::iterator> hash;
public:
	FirstUnique(vector<int>& nums) {
		for (int i = 0; i < nums.size(); ++i) {
			add(nums[i]);
		}
	}

	int showFirstUnique() {
		if (uniques_.empty())return -1;
		else return uniques_.front();
	}

	void add(int value) {
		if (hash.find(value) == hash.end()) {
			uniques_.push_back(value);
			hash[value] = --uniques_.end();
		}
		else {
			if (hash[value] != uniques_.end()) {
				uniques_.erase(hash[value]);
				hash[value] = uniques_.end();
			}
		}
	}
};

本代码提交AC,用时520MS。

LeetCode LRU Cache

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.

The cache is initialized with a positive capacity.

Follow up:
Could you do both operations in O(1) time complexity?

Example:

LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4

实现一个LUR cache。 我们知道数据的访问都有时间局部性和空间局部性,所以为了加快访问速度,把经常访问的数据放到高速缓存cache中,这样先查cache,不命中再查内存或者外存。 但是cache一般都很小,当新数据需要插入cache时,如果cache满,则需要替换掉cache中旧的元素。此时替换哪个元素就有很多策略了,可以把最久没访问的替换出去,也可以把访问频率最低的替换出去,还可以把最先进入的替换出去等等,各种策略就形成了不同的缓存替换算法。 LRU的意思是Least Recently Used (LRU),即把最近最久没访问的元素替换出去,是指时间上最久没访问的,而不是频率上访问最少的哦。 题目要求实现LRU的两个接口,分别是

  1. get(key),从LRU中获取key对应的value,如果LRU中不存在key,则返回-1
  2. put(key,value),把key和对应的value插入到LRU中,如果LRU满,则要替换掉最近最久没访问的元素

要求这两个操作都在O(1)时间内完成。 我们来分析一下应该用哪些数据结构使这两个操作都在O(1)时间内完成。首先get需要在O(1)时间内判断key是否在LRU中,所以可能需要用hash来存储<key,value>对。其次,需要缓存替换时,要在O(1)时间内把最久没访问的<key,value>删掉,同时插入新的<key,value>,删除和插入能在O(1)时间内完成的就是链表了。

综合上面的分析,我们可以用unordered_map+list来实现LRU,STL的list是一个双向链表。 参考这份代码。我们使用list<int> used来保存当前在LRU中的那些key,注意是key;用unordered_map<int, pair<int, list<int>::iterator>> cache来保存真实的缓存,cache的key就是LRU中现存的key,value是一个pair,保存了key对应的value,以及这个key在链表中的迭代器(指针)。

两个操作的实现如下:

put(key,value)。每当有新<key,value>进入时,如果key已经在cache中,则只需要更新key对应的value,同时,刚访问的元素下次很可能再次访问,所以需要把这个key提到链表的表头(为了不让这个key沉到表尾)。如果key不在cache中,但cache已满,则需要把链表尾部的key对应的数据在cache中删掉,同时也需要在链表中删掉这个尾节点;如果cache不满,则把新key插入到链表表头,同时把<key,value>插入到cache中,且key在链表中的位置是list.begin()。

get(key)。如果key不在cache中,直接返回-1。如果key在cache中,直接从cache中得到对应的value,同时根据迭代器,把key提到链表表头。 根据上述算法描述,我们需要抽象一个函数,moveToHead(list<int>::iterator &it),即把迭代器it指向的那个key移到表头,包括两个操作,把it从it位置删掉,把it的值插到表头。

完整代码如下:

class LRUCache {
private:
    unordered_map<int, pair<int, list<int>::iterator> > cache;
    list<int> used;
    int _capacity; // 把it节点**剪切**到表头
    void moveToHead(list<int>::iterator& it)
    {
        int key = *it;
        used.erase(it);
        used.push_front(key);
        cache[key].second = used.begin();
    }
public:
    LRUCache(int capacity)
        : _capacity(capacity)
    {
    }
    int get(int key)
    {
        if (cache.find(key) == cache.end())
            return -1;
        int value = cache[key].first;
        moveToHead(cache[key].second);
        return value;
    }
    void put(int key, int value)
    {
        if (cache.find(key) != cache.end()) {
            cache[key].first = value;
            moveToHead(cache[key].second);
        }
        else {
            if (used.size() == _capacity) {
                cache.erase(used.back());
                used.pop_back();
            }
            used.push_front(key);
            cache[key] = { value, used.begin() };
        }
    }
};

本代码提交AC,用时96MS。

我们还可以自己实现list,就是一个双向链表。这个双向链表包括头结点head和尾节点tail。为了插入删除的方便,我们设置了一个用不到的head和tail,所以真正的数据其实是在head和tail之间的,如下,节点1~3才是真正的数据节点

头 --> 节 --> 节 --> 节 --> 尾
节     点     点     点     节
点 <-- 1  <-- 2 <-- 3  <-- 点

这里我们抽象出了3个函数,removeFromList和moveToHead完成上一种实现中的moveToHead功能,即先把节点从原链表中删除(相当于摘下来),然后把这个节点安放到表头(上图节点1)。removeTail就是在需要替换时,删除表尾节点(上图节点3)。

完整代码如下:

class LRUCache {
private:
    struct Node {
        int key, value;
        Node *pre, *next;
        Node(int k, int v)
            : key(k)
            , value(v)
            , pre(NULL)
            , next(NULL){};
    };
    unordered_map<int, Node*> cache;
    Node *head, *tail;
    int _capacity; // 从原链表中删除节点node(摘下)
    void removeFromList(Node* node)
    {
        node->pre->next = node->next;
        node->next->pre = node->pre;
    }
    // 把节点node放到“表头”(安放)
    void moveToHead(Node* node)
    {
        node->next = head->next;
        head->next->pre = node;
        node->pre = head;
        head->next = node;
    }
    // 删除“尾节点”
    void removeTail()
    {
        Node* target = tail->pre;
        target->pre->next = target->next;
        target->next->pre = target->pre;
        delete target;
    }

public:
    LRUCache(int capacity)
        : _capacity(capacity)
    {
        head = new Node(0, 0); // 辅助头结点
        tail = new Node(0, 0); // 辅助尾节点
        head->next = tail;
        tail->pre = head;
    }
    int get(int key)
    {
        if (cache.find(key) == cache.end())
            return -1;
        int value = cache[key]->value;
        removeFromList(cache[key]);
        moveToHead(cache[key]);
        return value;
    }
    void put(int key, int value)
    {
        if (cache.find(key) != cache.end()) {
            cache[key]->value = value;
            removeFromList(cache[key]);
            moveToHead(cache[key]);
        }
        else {
            if (cache.size() == _capacity) {
                cache.erase(tail->pre->key);
                removeTail();
            }
            Node* node = new Node(key, value);
            cache[key] = node;
            moveToHead(node);
        }
    }
};

本代码提交AC,用时98MS。