Medium
Design a data structure that follows the constraints of a Least Recently Used (LRU) cache.
Implement the LRUCache class:
LRUCache(int capacity) Initialize the LRU cache with positive size capacity.int get(int key) Return the value of the key if the key exists, otherwise return -1.void put(int key, int value) Update the value of the key if the key exists. Otherwise, add the key-value pair to the cache. If the number of keys exceeds the capacity from this operation, evict the least recently used key.The functions get and put must each run in O(1) average time complexity.
Example 1:
Input [“LRUCache”, “put”, “put”, “get”, “put”, “get”, “put”, “get”, “get”, “get”] [[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
Output: [null, null, null, 1, null, -1, null, -1, 3, 4]
Explanation:
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); // cache is {1=1}
lRUCache.put(2, 2); // cache is {1=1, 2=2}
lRUCache.get(1); // return 1
lRUCache.put(3, 3); // LRU key was 2, evicts key 2, cache is {1=1, 3=3}
lRUCache.get(2); // returns -1 (not found)
lRUCache.put(4, 4); // LRU key was 1, evicts key 1, cache is {4=4, 3=3}
lRUCache.get(1); // return -1 (not found)
lRUCache.get(3); // return 3
lRUCache.get(4); // return 4
Constraints:
1 <= capacity <= 30000 <= key <= 1040 <= value <= 105 * 105 calls will be made to get and put.To solve the “LRU Cache” problem, we need to implement a data structure that efficiently supports the get and put operations while maintaining the least recently used (LRU) policy. We can achieve this using a combination of a dictionary and a doubly linked list.
LRUCache with an initializer that takes the capacity of the cache.Node class for the doubly linked list nodes, which includes properties for the key, value, previous node, and next node.Here’s the Swift implementation of the LRUCache class:
class LRUCache {
private class Node {
var key: Int
var value: Int
var prev: Node?
var next: Node?
init(_ key: Int, _ value: Int) {
self.key = key
self.value = value
}
}
private var capacity: Int
private var dict = [Int: Node]()
private var head = Node(0, 0)
private var tail = Node(0, 0)
init(_ capacity: Int) {
self.capacity = capacity
head.next = tail
tail.prev = head
}
func get(_ key: Int) -> Int {
if let node = dict[key] {
moveToHead(node)
return node.value
} else {
return -1
}
}
func put(_ key: Int, _ value: Int) {
if let node = dict[key] {
node.value = value
moveToHead(node)
} else {
let newNode = Node(key, value)
dict[key] = newNode
addNode(newNode)
if dict.count > capacity {
if let tail = removeTail() {
dict.removeValue(forKey: tail.key)
}
}
}
}
private func addNode(_ node: Node) {
node.prev = head
node.next = head.next
head.next?.prev = node
head.next = node
}
private func removeNode(_ node: Node) {
let prev = node.prev
let next = node.next
prev?.next = next
next?.prev = prev
}
private func moveToHead(_ node: Node) {
removeNode(node)
addNode(node)
}
private func removeTail() -> Node? {
let node = tail.prev
if let node = node {
removeNode(node)
}
return node
}
}
Node class represents each node in the doubly linked list, with key, value, prev, and next properties.addNode(_:) inserts a node right after the head.removeNode(_:) removes a node from its current position.moveToHead(_:) removes a node and then adds it right after the head.removeTail() removes the node just before the tail, which is the least recently used node.get(_:) method checks if the key exists in the dictionary. If it does, it moves the node to the head and returns its value. Otherwise, it returns -1.put(_:) method updates the node’s value if the key exists and moves it to the head.This implementation ensures that both get and put operations run in O(1) time complexity, adhering to the problem constraints.