Both caches receive the same stream of requests. When a cache is full, LRU evicts the key used longest ago and LFU evicts the key used least often. Step through a scenario and watch which one keeps the right keys.
A hash map plus a doubly linked list ordered by recency. A hit moves the key to the front; a miss inserts at the front and drops the tail when full. Both are O(1).
get(k): if k in map: move k to front; return hit
put(k): if full: remove tail
insert k at front
Each key keeps a use count. When full, the key with the lowest count is evicted; ties go to the one used longest ago. O(1) versions keep a list of keys per count.
get(k): if k in map: count[k] += 1; return hit
put(k): if full: remove key with min (count, last_used)
insert k with count 1