LRU를 사용하여 LFU 구현

발행: (2026년 4월 2일 오후 06:27 GMT+9)
5 분 소요
원문: Dev.to

I’m happy to help translate the article, but I’ll need the full text you’d like translated. Could you please paste the content (excluding the source line you already provided) here? Once I have the article, I’ll translate it into Korean while preserving the original formatting, markdown, and any code blocks or URLs.

📌 Core Idea

LFU (Least Frequently Used) means:

  • Remove the least frequently used key.
  • If multiple keys have the same frequency → remove the least recently used (LRU) among them.

📦 사용된 데이터 구조

  • key → node (O(1) 접근을 위해)
  • freq → doubly linked list (노드를 빈도별로 그룹화)
  • minFreq (캐시에서 최소 빈도를 추적)

🧱 구조 시각화

Freq = 1 → [ most recent … least recent ]
Freq = 2 → [ most recent … least recent ]
Freq = 3 → [ most recent … least recent ]

각 주파수는 자체 LRU 리스트를 가집니다.

🚀 예시 진행 과정

용량 = 2

  1. put(1,10)

    • Freq 1: [1]
    • minFreq = 1
  2. put(2,20)

  3. get(1) → 키 1에 접근

    • 빈도 증가: 1 → 2
    • Freq 1: [2] Freq 2: [1]
    • minFreq = 1
  4. put(3,30) (캐시 가득)

    • minFreq = 1 → freq 1에서 LRU(2)를 제거
    • 삽입 후:
      • Freq 1: [3] Freq 2: [1]
      • minFreq = 1
  5. get(3)3을 freq 1에서 freq 2로 이동

    • Freq 1: [] Freq 2: [3, 1]
    • minFreq = 2 (freq 1이 비어 있음)
  6. put(4,40) (캐시 가득)

    • minFreq = 2 → freq 2에서 LRU(1)를 제거
    • 삽입 후:
      • Freq 1: [4] Freq 2: [3]
      • minFreq = 1

🔥 주요 관찰

  1. 왜 여러 개의 리스트가 필요한가?
    서로 다른 빈도는 하나의 리스트에 저장할 수 없으며, 각 빈도마다 자체적인 LRU 순서를 유지합니다.

  2. minFreq인가?
    이를 통해 O(1) 시간에 제거할 리스트를 찾을 수 있습니다.

  3. 왜 이중 연결 리스트인가?
    동일한 빈도 내에서 LRU 순서를 유지하면서 O(1) 시간에 삭제할 수 있게 해줍니다.

🧠 정신 모델

LFU를 책이 놓인 선반에 비유해 보세요:

  • 선반 1 → 가장 적게 사용된 책
  • 선반 2 → 적당히 사용된 책
  • 선반 3 → 가장 많이 사용된 책

제거할 때는 가장 적게 사용된 선반을 선택하고, 해당 선반에서 가장 오래된 책을 버립니다.

🧩 Mapping to Code

ConceptCode
빈도 증가updateFreq(node)
LRU 제거list->tail->prev
최소값 추적minFreq
새 노드 삽입frequency = 1

🚨 일반적인 실수

빈도 리스트가 비게 되면 minFreq를 업데이트해야 합니다:

if (freq == minFreq && list->size == 0)
    minFreq++;

minFreq를 업데이트하지 않으면 교체(eviction)가 정상적으로 동작하지 않습니다.

코드

class LFUCache {
public:
    class Node {
    public:
        int key, val, freq;
        Node* prev;
        Node* next;
        Node(int k, int v) : key(k), val(v), freq(1), prev(nullptr), next(nullptr) {}
    };

    class List {
    public:
        Node* head;
        Node* tail;
        int size;
        List() : size(0) {
            head = new Node(-1, -1);
            tail = new Node(-1, -1);
            head->next = tail;
            tail->prev = head;
        }
        void addFront(Node* node) {
            Node* nxt = head->next;
            node->next = nxt;
            node->prev = head;
            head->next = node;
            nxt->prev = node;
            ++size;
        }
        void removeNode(Node* node) {
            Node* prv = node->prev;
            Node* nxt = node->next;
            prv->next = nxt;
            nxt->prev = prv;
            --size;
        }
    };

    int cap;
    int minFreq;
    unordered_map keyNode;   // key → node
    unordered_map freqList;  // freq → DLL

    LFUCache(int capacity) : cap(capacity), minFreq(0) {}

    void updateFreq(Node* node) {
        int f = node->freq;
        freqList[f]->removeNode(node);
        if (f == minFreq && freqList[f]->size == 0)
            ++minFreq;

        ++node->freq;
        if (!freqList.count(node->freq))
            freqList[node->freq] = new List();
        freqList[node->freq]->addFront(node);
    }

    int get(int key) {
        if (!keyNode.count(key)) return -1;
        Node* node = keyNode[key];
        updateFreq(node);
        return node->val;
    }

    void put(int key, int value) {
        if (cap == 0) return;
        if (keyNode.count(key)) {
            Node* node = keyNode[key];
            node->val = value;
            updateFreq(node);
            return;
        }
        if (keyNode.size() == cap) {
            List* list = freqList[minFreq];
            Node* evict = list->tail->prev;
            keyNode.erase(evict->key);
            list->removeNode(evict);
        }
        Node* newNode = new Node(key, value);
        minFreq = 1;
        if (!freqList.count(1))
            freqList[1] = new List();
        freqList[1]->addFront(newNode);
        keyNode[key] = newNode;
    }
};
0 조회
Back to Blog

관련 글

더 보기 »

Python에서 행렬

행렬 정의 python matrix = 1, 2, 3, 4, 5, 6, 7, 8, 9 3x3 행렬 만들기 python matrix_3x3 = 0 3 for in range3 일반적인 행렬 문제 행렬 전치…

PWC 367 중첩된 이상현상

Task 1 – Maximum Odd Binary 아르테미스 2 미션이 달에 가는 주간이며, 우리는 홀수에 관한 문제를 가지고 있습니다. 나는 이것을 Space Oddity라고 부를 것입니다. 당신은…

주간 챌린지: 최대 충돌

마크다운 !Simon Greenhttps://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fu...

JavaScript에서 Map과 Set

Map이란? Map은 키‑값 쌍의 컬렉션으로, 객체와 비슷하지만 여러 가지 개선점이 있습니다: - 키는 어떤 타입의 객체, 함수, 원시값도 될 수 있습니다.