package com.freetymekiyan.algorithms.level.hard; import java.util.HashMap; import java.util.Map; /** * Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following * operations: get and set. * <p> * get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. * set(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. * <p> * Company Tags: Google, Uber, Facebook, Twitter, Zenefits, Amazon, Microsoft, Snapchat, Yahoo, Bloomberg, Palantir * Tags: Design * <p> * Use 2 data structures to implement an LRU Cache: * 1. A queue which is implemented by a doubly linked list. The max size of the queue will be equal to cache size. Put * least recently used at the tail. * 2. A hash map with Node's value as key and the Node as value. * 3. A dummy head and a dummy tail of the queue. So that we can access both head and tail fairly quick. */ class LRUCache { private final int capacity; private final Map<Integer, Node> cache; private final Node head; private final Node tail; /** * Remember capacity. * Create cache map and double linked list. */ public LRUCache(int capacity) { this.capacity = capacity; cache = new HashMap<>(); head = new Node(); tail = new Node(); head.next = tail; tail.prev = head; } /** * Check key in cache. * If not in cache, return -1. * If in cache, get the node, move it to head, return its value. */ public int get(int key) { if (!cache.containsKey(key)) { return -1; } Node node = cache.get(key); moveToHead(node); return node.val; } /** * If key already in cache: * | Get the node, update its value, move to head. * If key is not in cache: * | Create a new node. * | Add it to list and cache. * | If cache size exceeds capacity: * | Get the last node. * | Remove it from list and cache. */ public void set(int key, int value) { if (cache.containsKey(key)) { Node node = cache.get(key); node.val = value; moveToHead(node); } else { Node newNode = new Node(key, value); addNode(newNode); cache.put(key, newNode); if (cache.size() > capacity) { Node last = tail.prev; removeNode(last); cache.remove(last.key); } } } /** * Remove node from list and add it to head. */ private void moveToHead(Node node) { removeNode(node); addNode(node); } /** * Remove a node from double linked list. */ private void removeNode(Node node) { node.prev.next = node.next; node.next.prev = node.prev; } /** * Add a node after head. */ private void addNode(Node node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } /** * Double linked list node. * With previous node, next node, key, and value. */ class Node { Node prev; Node next; int key; int val; public Node() { } public Node(int key, int val) { this.key = key; this.val = val; } } }