🧠
Brute Force (Using HashMap and Sorting on Eviction)
💡 This approach exists to help understand the problem by implementing the eviction logic in the simplest way possible, even if inefficient. It clarifies the eviction criteria and frequency tracking. Think of it as a first draft or prototype before optimizing.
Intuition
Store all keys with their frequencies and timestamps. On eviction, scan all keys to find the least frequently used and least recently used key to remove.
Algorithm
- Use a hashmap to store key to (value, frequency, timestamp).
- On get(key), update frequency and timestamp, return value or -1 if not found.
- On put(key, value), if key exists, update value, frequency, timestamp.
- If capacity exceeded, scan all keys to find the LFU and LRU key to evict.
- Insert new key with frequency 1 and current timestamp.
💡 The main difficulty is the eviction step which requires scanning all keys, making it inefficient but conceptually simple.
import time
class LFUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.time = 0
self.cache = {} # key: (value, freq, time)
def get(self, key: int) -> int:
if key not in self.cache:
return -1
value, freq, _ = self.cache[key]
self.time += 1
self.cache[key] = (value, freq + 1, self.time)
return value
def put(self, key: int, value: int) -> None:
if self.capacity == 0:
return
self.time += 1
if key in self.cache:
_, freq, _ = self.cache[key]
self.cache[key] = (value, freq + 1, self.time)
return
if len(self.cache) == self.capacity:
# Evict LFU and LRU
min_freq = min(freq for _, freq, _ in self.cache.values())
candidates = [k for k, (_, freq, _) in self.cache.items() if freq == min_freq]
lru_key = min(candidates, key=lambda k: self.cache[k][2])
del self.cache[lru_key]
self.cache[key] = (value, 1, self.time)
# Driver code
if __name__ == '__main__':
cache = LFUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1)) # returns 1
cache.put(3, 3) # evicts key 2
print(cache.get(2)) # returns -1
print(cache.get(3)) # returns 3
cache.put(4, 4) # evicts key 1
print(cache.get(1)) # returns -1
print(cache.get(3)) # returns 3
print(cache.get(4)) # returns 4
Line Notes
self.cache = {} # key: (value, freq, time)Stores all keys with their value, frequency, and last access time to track usage.
if key not in self.cache:Check if key exists before returning value to handle cache misses.
self.time += 1Increment global time to track recency and maintain order for eviction.
min_freq = min(freq for _, freq, _ in self.cache.values())Find minimum frequency among all keys to identify eviction candidates.
candidates = [k for k, (_, freq, _) in self.cache.items() if freq == min_freq]Collect all keys with minimum frequency to break ties.
lru_key = min(candidates, key=lambda k: self.cache[k][2])Among candidates, find least recently used key by timestamp for eviction.
del self.cache[lru_key]Evict the identified key from cache to make space for new entries.
import java.util.*;
class LFUCache {
private int capacity, time;
private Map<Integer, int[]> cache; // key -> {value, freq, time}
public LFUCache(int capacity) {
this.capacity = capacity;
this.time = 0;
this.cache = new HashMap<>();
}
public int get(int key) {
if (!cache.containsKey(key)) return -1;
int[] val = cache.get(key);
val[1]++;
val[2] = ++time;
return val[0];
}
public void put(int key, int value) {
if (capacity == 0) return;
time++;
if (cache.containsKey(key)) {
int[] val = cache.get(key);
val[0] = value;
val[1]++;
val[2] = time;
return;
}
if (cache.size() == capacity) {
int minFreq = Integer.MAX_VALUE;
for (int[] v : cache.values()) {
minFreq = Math.min(minFreq, v[1]);
}
int lruKey = -1;
int oldestTime = Integer.MAX_VALUE;
for (Map.Entry<Integer, int[]> entry : cache.entrySet()) {
int[] v = entry.getValue();
if (v[1] == minFreq && v[2] < oldestTime) {
oldestTime = v[2];
lruKey = entry.getKey();
}
}
cache.remove(lruKey);
}
cache.put(key, new int[]{value, 1, time});
}
// Main method for testing
public static void main(String[] args) {
LFUCache cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
System.out.println(cache.get(1)); // 1
cache.put(3, 3); // evicts key 2
System.out.println(cache.get(2)); // -1
System.out.println(cache.get(3)); // 3
cache.put(4, 4); // evicts key 1
System.out.println(cache.get(1)); // -1
System.out.println(cache.get(3)); // 3
System.out.println(cache.get(4)); // 4
}
}
Line Notes
private Map<Integer, int[]> cache;Stores key mapped to array of value, frequency, and timestamp for quick access and updates.
if (!cache.containsKey(key)) return -1;Return -1 if key not found to indicate cache miss.
val[1]++;Increment frequency on access to track usage count.
val[2] = ++time;Update timestamp to current time to maintain recency order.
int minFreq = Integer.MAX_VALUE;Initialize min frequency to find eviction candidates among all keys.
if (v[1] == minFreq && v[2] < oldestTime)Find least recently used key among those with min frequency for eviction.
cache.remove(lruKey);Remove the identified key from cache to free space.
#include <iostream>
#include <unordered_map>
#include <climits>
using namespace std;
class LFUCache {
int capacity, time;
unordered_map<int, tuple<int, int, int>> cache; // key -> (value, freq, time)
public:
LFUCache(int capacity) : capacity(capacity), time(0) {}
int get(int key) {
if (cache.find(key) == cache.end()) return -1;
auto &[value, freq, t] = cache[key];
freq++;
t = ++time;
return value;
}
void put(int key, int value) {
if (capacity == 0) return;
time++;
if (cache.find(key) != cache.end()) {
auto &[val, freq, t] = cache[key];
val = value;
freq++;
t = time;
return;
}
if ((int)cache.size() == capacity) {
int minFreq = INT_MAX;
for (auto &[k, v] : cache) {
int freq = get<1>(v);
if (freq < minFreq) minFreq = freq;
}
int lruKey = -1;
int oldestTime = INT_MAX;
for (auto &[k, v] : cache) {
int freq = get<1>(v);
int t = get<2>(v);
if (freq == minFreq && t < oldestTime) {
oldestTime = t;
lruKey = k;
}
}
cache.erase(lruKey);
}
cache[key] = make_tuple(value, 1, time);
}
};
int main() {
LFUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << "\n"; // 1
cache.put(3, 3); // evicts key 2
cout << cache.get(2) << "\n"; // -1
cout << cache.get(3) << "\n"; // 3
cache.put(4, 4); // evicts key 1
cout << cache.get(1) << "\n"; // -1
cout << cache.get(3) << "\n"; // 3
cout << cache.get(4) << "\n"; // 4
return 0;
}
Line Notes
unordered_map<int, tuple<int, int, int>> cache;Maps key to tuple of value, frequency, and timestamp for efficient lookups.
if (cache.find(key) == cache.end()) return -1;Return -1 if key not found to indicate cache miss.
freq++;Increment frequency on access to track usage count.
t = ++time;Update timestamp to current time to maintain recency order.
int minFreq = INT_MAX;Find minimum frequency for eviction candidates among all keys.
if (freq == minFreq && t < oldestTime)Find least recently used key among minimum frequency keys for eviction.
cache.erase(lruKey);Remove the identified key from cache to free space.
class LFUCache {
constructor(capacity) {
this.capacity = capacity;
this.time = 0;
this.cache = new Map(); // key -> {value, freq, time}
}
get(key) {
if (!this.cache.has(key)) return -1;
let entry = this.cache.get(key);
entry.freq++;
this.time++;
entry.time = this.time;
return entry.value;
}
put(key, value) {
if (this.capacity === 0) return;
this.time++;
if (this.cache.has(key)) {
let entry = this.cache.get(key);
entry.value = value;
entry.freq++;
entry.time = this.time;
return;
}
if (this.cache.size === this.capacity) {
let minFreq = Infinity;
for (let entry of this.cache.values()) {
if (entry.freq < minFreq) minFreq = entry.freq;
}
let lruKey = null;
let oldestTime = Infinity;
for (let [k, entry] of this.cache.entries()) {
if (entry.freq === minFreq && entry.time < oldestTime) {
oldestTime = entry.time;
lruKey = k;
}
}
this.cache.delete(lruKey);
}
this.cache.set(key, {value: value, freq: 1, time: this.time});
}
}
// Test
const cache = new LFUCache(2);
cache.put(1, 1);
cache.put(2, 2);
console.log(cache.get(1)); // 1
cache.put(3, 3); // evicts key 2
console.log(cache.get(2)); // -1
console.log(cache.get(3)); // 3
cache.put(4, 4); // evicts key 1
console.log(cache.get(1)); // -1
console.log(cache.get(3)); // 3
console.log(cache.get(4)); // 4
Line Notes
this.cache = new Map();Stores key mapped to object with value, frequency, and timestamp for quick access and updates.
if (!this.cache.has(key)) return -1;Return -1 if key not found to indicate cache miss.
entry.freq++;Increment frequency on access to track usage count.
this.time++;Increment global time to track recency and maintain order for eviction.
let minFreq = Infinity;Find minimum frequency among all keys for eviction candidates.
if (entry.freq === minFreq && entry.time < oldestTime)Find least recently used key among minimum frequency keys for eviction.
this.cache.delete(lruKey);Remove the identified key from cache to free space.
TimeO(n) per put in worst case due to scanning all keys for eviction
SpaceO(capacity) for storing cache entries
Each put operation may require scanning all keys to find the LFU and LRU key, leading to linear time complexity per put. Get operations are O(1) as they directly access the hashmap.
💡 For n=1000 puts, this approach could do up to 1,000,000 operations, which is too slow for large inputs. This highlights why optimization is necessary.
Interview Verdict: TLE / Use only to introduce problem and eviction logic
This approach is too slow for large inputs but helps understand the eviction criteria and frequency tracking before optimizing.