这题难死了。另外我完全放弃非口语化描述。我日常怎么说话笔记就怎么写了。方便自己理解。上次写这题是 4 月 28 日,记得代码行数很多,现在的话,重写没有任何勇气。目前是写了 38 题了。每天至多写一个新题,可以不断复习之前写过的题,也就是前面 38 题。感觉前面的题,都写不了了。反正目标就是写 100 题,多的题,一个都不写。反正。。。算了。菜是我的原罪。我可以看自己写的笔记来复习写过的算法题。这是自己的独家复习资料。自己用起来最称手。看了以下是 66 行代码,吓哭了。华语女歌手精选这个歌单好听。
- key 和 val 是结构体 node 里面的两个参数,表示键和值。感觉和哈希表有点像。但是不是一个东西吧。因为哈希表是常数时间访问的容器。
- 结构体里面定义了两个指针,这是一个新的操作,实际上也非常常见,就是双向链表。感觉主要是要学这种写法。结构体后面要加一个分号。结构体一般的定义后面,也就是把参数设置为默认值的那一行,不需要加分号。设置参数的行,括号里面表示两个传入的参数,随便写,但是需要定义正确类型,括号后面要有冒号,指针不需要加 *
- 定义好几个结构体里面的参数就可以了。
- capacity & size 是干啥的。我有点忘记了。capacity 应该是 cache 的容量,size 应该是实际的大小,应该就是 size 超过 capacity 就需要换掉。点题,LRU 就是因为不够缓存,就把最近最久未使用的换出去。least recently used 。
- *head *tail 都是用的我们前面定义的那个结构体。等我写完这个题就去休息。我废了。
- *head *tail 其实是虚拟节点。奥也不是节点?应该是节点。就是最开始肯定是 *head 指向 *tail ,*tail 指向 *head ,然后往中间加东西。*head 和 *tail 不存实际的东西。和那个 dummy 很类似。孙燕姿的 180 度真好听啊。
- 定义了一个哈希表,哈希表用 cache 表示,即缓存。参数是 int 和 Node* ,也就是说,可以通过 int 常数时间找到 Node*, 有点不理解。这是啥呢。奥。这是模拟 cache 的高速,就是我们访存比较慢速,但是我们访问 cache 是非常快的,几乎可以认为是常数时间。哈希表明显就是常数时间可以通过键找到所谓的值。这里的键就是这里的 int ,值就是这里的 Node*.
- 下面是四个可怕的辅助函数。实际上每个函数也就几行。主要是多。复杂的工程就是简单的东西拼凑起来的,简单的东西多了,整个工程就难了。很有哲理的。
- 删除有点意思。通俗地说,没有实际地删除 node 这个节点,只是修改了指针,没有实际地 delete node ,当然从释放内存角度考虑,最后肯定还是要释放这个节点的内存的。所以我们的 public 里面是有 delete 这个操作的。把节点的前面的节点的 next 指针指向节点的后面一个节点,节点的后面一个节点的 prev 指针指向节点的前面一个节点,等效删除。
- 下一个函数是添加到头节点。我自己想以下哈。首先是,如果头节点是这个节点就直接结束就行。如果不是呢。但是这一行我不会写。我不知道 head 指向的是啥。实际上应该还有一点是,node 的 next 指向 head 的 next ,head 的 next 的 prev 指向 node ,然后 head 的 next 指向 node 应该就可以了。但是这里只有三行?我好像不小心看到有四行代码了。肯定就是我不会写的那行占领了一行?
- 我傻逼了。是少了 node 的 prev 的指向。
- 删除到头节点,我没看懂是啥意思,仔细看了代码,做的操作是删除节点,然后把这个节点添加到头节点,实际上就是说,访问了一次这个节点吧。如果在链表头部,认为是最近使用过了。很多东西就是要使用才灵活,比如说数学题算法题。最近最久未使用是每次都淘汰尾部的节点。最近使用并且提升到头节点的,肯定是最后才被删除的。我感觉自己理解得差不多了。
- 这里的删除尾部元素,使用了 *tail 这个哨兵,*tail 不是实际的尾部,*tail 前面的才是,双向链表可以解决这个问题。然后的话,我们定义的 remove 函数只修改了指针,并没有对内存进行释放。所以我们可以直接调用这个 remove 函数修改指针,然后返回 res 表示实际的尾部节点。public 里面释放 res 的内存即可。就真的删除了这个尾部节点,修改指针 + 释放内存。
- this->capacity 是为了区分变量和传进来的参数。有点麻烦。有点费解。实际上可以把传进来的参数改一下。或者把变量名改一下。但是有点容易引起歧义。所以没改。就这样吧。
- this->size 是冗余的,size 就完全足够,并没有和任何参数冲突。
- 为啥 private 里面定义 head 和 tail 需要定义
Node *head, *tail,不可以直接定义为Node head, tail吗?*表示的是指针,加了指针表示存的是一个地址,不加指针表示存的是一个对象。 - head 里面存的是地址,*head 表示的是具体的对象。new 出来的是一个地址,地址赋值给 head 就是把地址存到 head 里面,*head 就可以得到这个地址的对象。
- head 是地址,所以可以直接写 head->next 和 tail->prev
- 如果对着 key 找,找不到,也就是找到了 cache 的 end 位置,就表示 cache 里面没有这个节点,那么就是查找失败了。就返回 -1 表示失败。
Node *node = cache[key]node 表示地址,*node 就是解地址,表示这个地址对应的对象。cache[key] 表示的是Node*本质上是一个地址。- 如果这个节点 get 到了就把这个节点添加到链表首部,优先级提高,然后返回这个节点的值。
- 更新的函数,如果找到了这个节点,就更新数值并且提升优先级,也就是把节点提升到链表首部。
- 没找到这个节点就新建一个节点,键和值都是我们给的。然后,把哈希表里面的 key 和 newNode 对应起来,这样可以建立一个哈希表,常数时间实现查询操作。把新添加的节点添加到头部,也就是优先级最高,最近刚使用过的。
- 增加 size 表示现在 cache 里面的节点变多了。cache 里面的 key 和 Node* 是一一对应的,Node 里面有 key and value ,Node 里面的 key 和 cache 里面的 key 是一个 key
- 豆包现在反应速度好慢。不知道是我电脑慢还是他慢。我服了。
- 当 size 大于 capacity 的时候,就是我们需要删除链表尾部节点的时候。这个时候,我们直接取出来尾部的节点,delete 就可以了。当然调用函数的时候修改了指针。
cache.erase(tailNode->key)不错,就是反向删除了。把节点的 key 找到,因为 cache 和 Node 的 key 是同一个。- 删除之后减小规模就可以了。挺难的。我觉得值得一个困难。哈哈哈。
- struct 定义参数那一行最后面还有 {}
- 实际运行好多小错误。难死我了。
- 写了 63 行,力竭了。。。
c
class LRUCache {
public:
LRUCache(int capacity) {
this->capacity = capacity;
this->size = 0;
head = new Node( 0, 0 );
tail = new Node( 0, 0 );
head->next = tail;
tail->prev = head;
}
int get(int key) {
if ( cache.find( key ) == cache.end() ) return -1;
Node *node = cache[key];
moveToHead( node );
return node->val;
}
void put(int key, int value) {
if ( cache.find( key ) != cache.end() ) {
Node *node = cache[key];
node->val = value;
moveToHead( node );
} else {
Node *newNode = new Node( key, value );
cache[key] = newNode;
addToHead( newNode );
size++;
if ( size > capacity ) {
Node *tailNode = removeTail();
cache.erase( tailNode->key );
delete tailNode;
size--;
}
}
}
private:
struct Node {
int key, val;
Node *next, *prev;
Node( int k, int v ) : key( k ), val( v ), next( nullptr ), prev( nullptr ) {}
};
int capacity, size;
Node *head, *tail;
unordered_map<int, Node*> cache;
void remove( Node *node ) {
node->prev->next = node->next;
node->next->prev = node->prev;
}
void addToHead( Node *node ) {
node->next = head->next;
head->next->prev = node;
head->next = node;
node->prev = head;
}
void moveToHead( Node* node ) {
remove( node );
addToHead( node );
}
Node *removeTail() {
Node *res = tail->prev;
remove( res );
return res;
}
};