[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.
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

Thought process:
Use a linked list to store the values and their orders in the cache. Use a hash map to map keys to list node. The linked list's order will be changing constantly. Get operation move the value requested to to head/tail of the list, to represent that it's most recently used. Put operation will sometimes require the tail/head list node to be removed.
To satisfy the above cases, I have two options:
  1. Use a doubly linked list, and map key directly to its corresponding node.
  2. Use a singly linked list, and map key to the previous node.
Using a singly-linked list:
  1. Get: check map.
    1. Key exists: move value to tail of the list. Return value.
    2. Key doesn't exist: return -1.
  2. Put:
    1. Key exists: set to new value, and move the node to the most recently used direction.
    2. Key doesn't exist:
      1. Insert node to the most recently used position.
      2. If capacity is exceeded, evict the least recently used item (head of list).

Solution 1 (singly-linked list):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
public class LRUCache {
    private class ListNode {
        int key;
        int value;
        ListNode next;
        ListNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    
    private ListNode dummy;
    private ListNode tail;
    private Map<Integer, ListNode> map;
    private int capacity;

    public LRUCache(int capacity) {
        dummy = new ListNode(0, 0);
        tail = dummy;
        map = new HashMap<>();
        this.capacity = capacity;
    }
    
    public int get(int key) {
        if (map.containsKey(key)) {
            ListNode previous = map.get(key);
            moveToTail(previous);
            
            return tail.value;
        } else {
            return -1;
        }
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            ListNode previous = map.get(key);
            previous.next.value = value;
            moveToTail(previous);
        } else {
            if (map.size() == capacity) {
                map.remove(dummy.next.key);
                dummy.next = dummy.next.next;
                if (dummy.next != null) {
                    map.put(dummy.next.key, dummy);
                }
            }
            ListNode put = new ListNode(key, value);
            tail.next = put;
            map.put(key, tail);
            tail = put;
        }
    }
    
    private void moveToTail(ListNode previous) {
        ListNode got = previous.next;
        if (got == tail) {
            return;
        }
        
        previous.next = got.next;
        if (previous.next != null) {
            map.put(previous.next.key, previous);
        }
        
        tail.next = got;
        got.next = null;
        map.put(got.key, tail);
        tail = got;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

Solution 2 (doubly-linked list):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class LRUCache {
    class ListNode {
        int key;
        int value;
        ListNode previous;
        ListNode next;
        
        ListNode(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }
    
    private ListNode head;
    private ListNode tail;
    private Map<Integer, ListNode> map;
    private int capacity;
    private int size;
    
    public LRUCache(int capacity) {
        head = new ListNode(0, 0);
        tail = new ListNode(0, 0);
        head.next = tail;
        tail.previous = head;
        map = new HashMap<>();
        this.capacity = capacity;
        size = 0;
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        ListNode node = map.get(key);
        moveToHead(node);
        return node.value;
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            ListNode node = map.get(key);
            node.value = value;
            moveToHead(node);
        } else {
            if (size == capacity) {
                evict();
                size--;
            }
            insert(key, value);
        }
    }
    
    private void moveToHead(ListNode node) {
        deleteNode(node);
        addToHead(node);
    }
    
    private void evict() {
        ListNode lru = tail.previous;
        deleteNode(lru);
        map.remove(lru.key);
    }
    
    private void insert(int key, int value) {
        ListNode node = new ListNode(key, value);
        addToHead(node);
        map.put(key, node);
        size++;
    }
    
    private void addToHead(ListNode node) {
        ListNode next = head.next;
        head.next = node;
        node.previous = head;
        node.next = next;
        next.previous = node;
    }
    
    private void deleteNode(ListNode node) {
        node.previous.next = node.next;
        node.next.previous = node.previous;
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache obj = new LRUCache(capacity);
 * int param_1 = obj.get(key);
 * obj.put(key,value);
 */

Time complexity: O(1) for both get and put.

Comments

Popular posts from this blog

[LeetCode] 714. Best Time to Buy and Sell Stock with Transaction Fee

[LeetCode] 269. Alien Dictionary

[LeetCode] 631. Design Excel Sum Formula