Design an O(1) eviction cache
Company: Palo Alto Networks
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Onsite
Quick Answer: This question evaluates data structure and algorithmic design skills within the Coding & Algorithms domain, focusing on cache eviction policies and techniques for achieving average O(1) get/put operations. It is commonly asked to test reasoning about time and space complexity, handling of capacity and edge cases, and demonstrates a blend of conceptual understanding and practical application.
Constraints
- 0 <= capacity <= 100000
- 0 <= len(ops) <= 200000
- Each op is either ["put", key, value] or ["get", key]
- Keys and values are 32-bit signed integers (e.g., -1e9 <= key,value <= 1e9)
- Average O(1) time for get and put
- Updates to existing keys must refresh recency
Solution
def lru_process(capacity: int, ops: list) -> list:
class Node:
__slots__ = ("key", "value", "prev", "next")
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, cap: int):
self.cap = max(0, cap)
self.map = {}
self.head = Node() # sentinel head (MRU side)
self.tail = Node() # sentinel tail (LRU side)
self.head.next = self.tail
self.tail.prev = self.head
def _add_front(self, node: Node) -> None:
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def _remove(self, node: Node) -> None:
p, n = node.prev, node.next
p.next = n
n.prev = p
node.prev = node.next = None
def _move_to_front(self, node: Node) -> None:
self._remove(node)
self._add_front(node)
def _pop_tail(self) -> Node:
if self.tail.prev is self.head:
return None
node = self.tail.prev
self._remove(node)
return node
def get(self, key: int) -> int:
if self.cap == 0:
return -1
node = self.map.get(key)
if node is None:
return -1
self._move_to_front(node)
return node.value
def put(self, key: int, value: int) -> None:
if self.cap == 0:
return
node = self.map.get(key)
if node is not None:
node.value = value
self._move_to_front(node)
return
node = Node(key, value)
self.map[key] = node
self._add_front(node)
if len(self.map) > self.cap:
evicted = self._pop_tail()
if evicted is not None:
del self.map[evicted.key]
cache = LRUCache(capacity)
out = []
for op in ops:
if not op:
continue
cmd = op[0]
if cmd == "get":
key = op[1]
out.append(cache.get(key))
elif cmd == "put":
key, value = op[1], op[2]
cache.put(key, value)
else:
# Unknown operation; ignore
pass
return out