在做 LeetCode - 146. LRU Cache 的时候用到了 list 的 splice 功能,做一个记录。
一、list::splice
先讲 splice 功能。splice 是剪接、胶接的意思,比如剪接电影、胶接磁带这样的,用在 list 上就是在一个 list 中剪接上另一个 list 的部分或全部内容。
英文介绍如下:
Transfer elements from list to list
Transfers elements from x into the container, inserting them at position.This effectively inserts those elements into the container and removes them from x, altering the sizes of both containers.
The operation does not involve the construction or destruction of any element.
They are transferred, no matter whether x is an lvalue or an rvalue, or whether the value_type supports move-construction or not.
就是说,这个功能是将一个 list Transfer 到另一个 list 中。这个功能在 C++98 和 C++11 中都是三种形式调用,不过 C++11 每一种都多了一个 右值引用(&&) 的参数传递方式,所以 C++11 中其实共有 6 种,不过还是分为 3 类:
用两个 list 举例:list la{ 1,2,3 }, lb{ 4,5,6 };
1、entire list(插入一整个 list) |
---|
void splice (const_iterator _Where, list& _Right) void splice (const_iterator _Where, list&& _Right) |
2、single element(插入 list 中的一个元素) |
void splice(const_iterator _Where, list& _Right, const_iterator _First) void splice(const_iterator _Where, list&& _Right, const_iterator _First) |
3、element range(插入 list 中的部分元素) |
// void splice(const_iterator _Where, list& _Right, const_iterator _First, const_iterator _Last) // void splice(const_iterator _Where, list&& _Right, const_iterator _First, const_iterator _Last) |
要注意两点:
1、三种 splice 方式都是在 position 位置插入,原 position 位置上的元素,在被插入元素的后边;
2、三种方式的 splice 后,被插入的元素会从原 list 中被 remove 掉
3、三种 splice 方式都不会有任何元素的构造和析构
4、splice 会影响两个 list 的大小和内容
5、右值引用的参数传递和非右值引用的参数传递结果是一样的
6、C++ STL list 的 splice 时间复杂度为
O
(
1
)
O(1)
O(1),因为这是特殊设计的,而 list 的 .size() 方法时间复杂度是
O
(
N
)
O(N)
O(N),因为要遍历整个链表(这个问题在另一篇博客中记录,待填入网址)。
还是用 list la{ 1,2,3 }, lb{ 4,5,6 };
举例,三种方式如下。
1、entire list
在一个 list 的 _Where
位置,插入另一个完整 list _Right
。
// void splice(const_iterator _Where, list& _Right)
la.splice(la.begin(), lb);
// void splice(const_iterator _Where, list&& _Right)
la.splice(la.begin(), std::move(lb)); // or la.splice(la.begin(), {4,5,6});
两种操作结果相同:la.size() = 6, lb.size() = 0
,la 内容为 4 5 6 1 2 3
,lb 为空。
所有右值引用传递参数的形式和上边的两种一样,后边就不写了。
2、single element
在一个 list 的 _Where
位置,插入另一个 list _Right
的 _First
元素(不一定是第一个,只是形参叫这个名字)。
// void splice(const_iterator _Where, list& _Right, const_iterator _First)
la.splice(la.begin(), lb, lb.begin());
splice 结果:la.size() = 4, lb.size() = 2
,la 内容为 4 1 2 3
,lb 为 5 6
。
3、element range
在一个 list 的 _Where
位置,插入另一个 list _Right
的 [_First, _Last)
元素(和 C++ 规定一样,左闭右开区间)。
// void splice(const_iterator _Where, list& _Right, const_iterator _First, const_iterator _Last)
auto ite = lb.begin();
++ite;
++ite;
la.splice(la.begin(), lb, lb.begin(), ite);
splice 结果:la.size() = 5, lb.size() = 1
,la 内容为 4 5 1 2 3
,lb 为 6
。
二、LRU cache - LeetCode - 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
代码使用 unordered_map<int, list<pair<int, int>>::iterator> mp;
来存储和查询 key,达到
O
(
1
)
O(1)
O(1) 时间复杂度,然后用 list<pair<int, int>> cacheList;
来存储 key 和对应的 value,因为链表的调整是
O
(
1
)
O(1)
O(1),所以 get 和 put 是
O
(
1
)
O(1)
O(1) 时间,代码如下:
LRUCache(int capacity) :_cap(capacity) {}
int get(int key) {
if(mp.find(key) == mp.end()) return -1;
const int res = mp[key]->second;
cacheList.splice(cacheList.begin(), cacheList, mp[key]);
mp[key] = cacheList.begin();
return res;
}
void put(int key, int value) {
if(mp.find(key) != mp.end()) {
mp[key]->second = value;
cacheList.splice(cacheList.begin(), cacheList, mp[key]);
mp[key] = cacheList.begin();
}
else {
if (cacheList.size() >= _cap) {
mp.erase(cacheList.back().first);
cacheList.pop_back();
}
cacheList.emplace_front(key, value);
mp[key] = cacheList.begin();
}
}
private:
int _cap;
list<pair<int, int>> cacheList;
unordered_map<int, list<pair<int, int>>::iterator> mp;
};