@@ -22,17 +22,20 @@ export { LRUCache };
2222// This class definition is provided by LeetCode and must be implemented by you. An LRU cache can be implemented
2323// using a hashmap and a linked list.
2424class LRUCache {
25- private head ?: Node ;
26-
27- private last ?: Node ;
28-
2925 private map : Map < number , Node > ;
30-
3126 private capacity : number ;
27+ private head : Node ;
28+ private tail : Node ;
3229
3330 constructor ( capacity : number ) {
3431 this . map = new Map ( ) ;
3532 this . capacity = capacity ;
33+
34+ // These are sentinel nodes that will never be directly accessed, but help us avoid certain undefined checks.
35+ this . head = new Node ( - 1 , - 1 ) ;
36+ this . tail = new Node ( - 1 , - 1 ) ;
37+ this . head . next = this . tail ;
38+ this . tail . previous = this . head ;
3639 }
3740
3841 get ( key : number ) : number {
@@ -44,105 +47,61 @@ class LRUCache {
4447 // storing the key on the node, we can just delete the last element from the list and then check for an undefined
4548 // value here in case we deleted the key from being at capacity.
4649 const node = this . map . get ( key ) ! ;
47-
48- // If the node is already at the front of the list, just remove it and return the value.
49- if ( node . key === this . head ?. key ) {
50- return node . value ;
51- }
52-
53- // Detach the node from its position in the list, and connect the previous and next nodes to each other instead.
54- this . unlink ( node ) ;
55-
56- // Move the node to the front of the list.
57- this . unshift ( node ) ;
58-
50+ this . moveToHead ( node ) ;
5951 return node . value ;
6052 }
6153
6254 put ( key : number , value : number ) : void {
6355 // If we already have this value in the map, just update it and don't bother mucking with the capacity.
6456 if ( this . map . has ( key ) ) {
6557 const node = this . map . get ( key ) ! ;
58+ this . moveToHead ( node ) ;
6659 node . value = value ;
67-
68- // Call get to update timestamp on the node, but throw away the result.
69- const _ = this . get ( key ) ;
7060 return ;
7161 }
7262
73- // If there's no capacity, we don't have to do anything.
74- if ( this . capacity <= 0 ) {
75- return ;
76- }
77-
78- // If we are at capacity, we will first have to evict an entry from the cache by finding the last accessed element
79- // from our list.
80- if ( this . map . size === this . capacity ) {
81- this . pop ( ) ;
82- }
83-
8463 const node = new Node ( key , value ) ;
8564 this . map . set ( key , node ) ;
86-
87- // I am assuming that a put will also update the last accessed timestamp of the node, so move it to the front of
88- // the list.
89- this . unshift ( node ) ;
90- }
91-
92- // Remove a node that is NOT the head node.
93- private unlink ( node : Node ) {
94- // If this was the last node, update the last pointer.
95- if ( node . key === this . last ?. key ) {
96- this . last = node . previous ! ;
97- this . last . next = undefined ;
98- return ;
99- }
100-
101- // Otherwise, this is not the last node and not the head node, so stitch the previous and next nodes together.
102- const left = node . previous ! ;
103- const right = node . next ;
104- left . next = right ;
105- if ( right !== undefined ) {
106- right . previous = left ;
65+ this . addToHead ( node ) ;
66+
67+ // If we've exceed capacity, we have to remove the least used element, aka the tail. At this point, there is
68+ // guaranteed to be at least one other element in the list because we just added one. Also, unless the capacity
69+ // is 0, we will always at least have another.
70+ //
71+ // So we can safely access this.tail.previous!
72+ if ( this . map . size > this . capacity ) {
73+ const tail = this . tail . previous ! ;
74+ this . removeNode ( tail ) ;
75+ this . map . delete ( tail . key ) ;
10776 }
10877 }
10978
110- // Unshift a node that is NOT the head node.
111- private unshift ( node : Node ) {
112- node . previous = undefined ;
79+ private addToHead ( node : Node ) : void {
80+ const a = this . head ;
81+ const b = node ;
82+ const c = this . head . next ;
11383
114- if ( this . head === undefined ) {
115- this . head = node ;
116- } else {
117- this . head . previous = node ;
118- node . next = this . head ;
119- this . head = node ;
120- }
84+ // Updates the pointers for the node itself.
85+ b . next = c ;
86+ b . previous = a ;
12187
122- if ( this . last === undefined ) {
123- this . last = node ;
124- }
88+ // Inserts the node at the front of the list.
89+ a . next = b ;
90+ c ! . previous = b ;
12591 }
12692
127- // Unlink the last node (also possibly the head node) and remove it from the map.
128- private pop ( ) {
129- const node = this . last ;
130-
131- if ( node === undefined ) {
132- return ;
133- }
134-
135- this . map . delete ( node . key ) ;
93+ private moveToHead ( node : Node ) : void {
94+ this . removeNode ( node ) ;
95+ this . addToHead ( node ) ;
96+ }
13697
137- // If this was the head node, just set both nodes to undefined and be done with it.
138- if ( node . key === this . head ?. key ) {
139- this . head = undefined ;
140- this . last = undefined ;
141- return ;
142- }
98+ private removeNode ( node : Node ) : void {
99+ const b = node ;
100+ const a = b . previous ! ;
101+ const c = b . next ! ;
143102
144- // Otherwise, unlink the last node.
145- this . unlink ( node ) ;
103+ a . next = c ;
104+ c . previous = a ;
146105 }
147106}
148107
0 commit comments