A cache that forgets what you have not used
MediumStuck?
Show a hintHide the hint
A hash map for finding, a doubly linked list for the order, and the map stores the node rather than the value so both are instant at once.
The idea behind this problem
Next in this lesson
The largest number in every windowBuild a cache holding at most capacity entries. Two operations arrive in order:
put key valuestores a value. If the cache is full, the least recently used entry is thrown out first.get keyreturns the stored value, or-1if it is not there.
Both put and get count as using an entry, so either one moves it to the most-recently-used end. Report the answer to each get, in order.
Worked example#
Capacity 2:
| operation | cache after, oldest first | answer |
|---|---|---|
| put 1 → 1 | 1 | |
| put 2 → 2 | 1 2 | |
| get 1 | 2 1 | 1 |
| put 3 → 3 | 1 3 | (2 was oldest, thrown out) |
| get 2 | 1 3 | -1 |
| put 4 → 4 | 3 4 | (1 was oldest, thrown out) |
| get 1 | 3 4 | -1 |
| get 3 | 4 3 | 3 |
| get 4 | 3 4 | 4 |
The idea#
Two requirements pull in different directions.
Finding a key must be instant. That is a hash map.
Knowing which entry is oldest must also be instant, and using an entry has to move it to the newest end without shifting anything else. A list would do it, but removing from the middle of an array is a walk.
The answer is to use both, pointing at each other:
- a doubly linked list in use order, oldest at the front, newest at the back;
- a hash map from key to the list node holding it.
Then get is: look the key up in the map to get the node, unlink it from wherever it is, and put it on the back. Unlinking is constant time because the node knows its own neighbours, which is exactly what a doubly linked list is for and why a singly linked one will not do.
Eviction is: take the node at the front, and remove its key from the map.
Why the map stores nodes, not values#
If it stored values, you could find the value fast and still have no idea where the entry sits in the order, so you would walk the list to find it. Storing the node is what makes both halves fast at once, and it is the trick worth taking away: two structures, each covering the other's weakness, joined by pointers.
About the input#
capacity, then the operations as a count and that many words, then a count and that many keys, then a count and that many values. A get still has a value in its slot; ignore it.
Constraints#
1 <= capacity <= 30001 <= operations <= 100000
Input
capacity, then operations as a count and that many words, then keys as a count and that many integers, then values the same way.
Output
A count is not printed. Print the answer to each get, separated by spaces.
Examples
Input
2 9 put put get put get put get get get 9 1 2 1 3 2 4 1 3 4 9 1 2 0 3 0 4 0 0 0
Output
1 -1 -1 3 4
Capacity two, so each put past the second throws out the least recently used.
Input
1 3 put put get 3 1 2 1 3 10 20 0
Output
-1
Capacity one: the second put evicts the first.
Input
2 2 get put 2 5 5 2 0 7
Output
-1
A get on an empty cache answers -1.
Something wrong with this problem? Report an issue
Ready to try it
Write your solution in C, C++, Java or Python and see every test case come back with the time and memory it took.
Free, and it stays free. No card.