Giới thiệu

Vào một ngày đẹp trời, bạn nảy ra ý tưởng là sẽ làm bánh để tặng người yêu, à không, 1 người bạn thân 😂. Vì vậy bạn quyết định đi đến nhà hàng, gặp ông đầu bếp mà bạn quen, hỏi ông ta về công thức làm món bánh mà bạn ưa thích. Lúc này, ông đầu bếp sẽ đi lấy cuốn sách công thức làm bánh được đặt ở trên kệ sách. Sau đó ông ta đặt cuốn sách lên bàn và tìm đúng công thức mà bạn cần, rồi chụp hình lại và đưa cho bạn. Cuối cùng thì cuốn sách sẽ được đặt lại vào kệ sách, ở đúng cái chỗ mà nó được lấy ra.

Ngày qua ngày, ông đầu bếp bắt đầu nhận ra càng có nhiều người cần đến công thức làm bánh của mình. Vì vậy ông ta quyết định đặt luôn cuốn sách trên bàn mà không cần phải đặt lại vào kệ nữa. Như vậy thì sẽ tiết kiệm thời gian của ông hơn. Nhưng ngoài công thức làm bánh ra, ông còn có sách về công thức làm sữa chua, cocktail, … và còn nhiều công thức khác nữa 😅. Cái bàn của ông thì nó không được to, chỉ đủ để 3 cuốn sách mà thôi. Vì vậy, ông chỉ có thể bày ra 3 cuốn sách có công thức mà thường được yêu cầu nhất mà thôi.

Vâng câu chuyện trên chỉ là 1 câu chuyện mà tôi bịa ra 😄. Nhưng mà nó có liên quan gì đến chủ đề của bài viết nhỉ? Thực ra là có đấy. Trong câu chuyện trên, tôi đã đề cập đến cái kệ sách, cái bàn, và ông đầu bếp. Trong kiến trúc của máy tính cũng như vậy: ông đầu bếp chính là cpu của máy tính, cái bàn chính là memory, còn cái kệ sách chính là disk. Data trên memory sẽ được truy xuất nhanh hơn đối với những data truy xuất từ disk. Nhưng mà bởi vì memory có kích thước nhỏ hơn rất nhiều so với disk, nên memory chỉ có thể cache lại những data thường được truy xuất mà thôi. Và để làm được điều này, Chúng ta sẽ cần 1 thuật toán có tên gọi là LRU (Least Recently Used) Cache.

LRU cache được mô tả với kịch bản như sau

lru-cache/Untitled.png

lru-cache/Untitled_01.png

lru-cache/Untitled%202.png

lru-cache/Untitled%203.png

lru-cache/Untitled%204.png

lru-cache/Untitled%205.png

Có rất nhiều thuật toán khác để giải quyết cho bài toán full cache. Trong bài viết này, chúng ta tập trung vào LRU bởi vì nó trường được sử dụng trong coding interview.

LRU cache sử dụng 2 cấu trúc dữ liệu là

lru-cache/Untitled%206.png

lru-cache/Untitled%207.png

Code 1 chút cho vui nào 😋

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package lib;

public class Node<T> {

private T data;
private Node<T> next;
private Node<T> previous;

public Node(T data) { this.data = data; }

public T getData() { return data; }

public Node<T> getNext() { return next; }

public void setNext(Node<T> next) { this.next = next; }

public boolean hasNext() { return next != null; }

public Node<T> getPrevious() { return previous; }

public void setPrevious(Node previous) { this.previous = previous; }

public boolean hasPrevious() { return previous != null; }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package lib;

public class DoubleLinkedList<T> {

private Node<T> first;
private Node<T> last;
private int size;

public DoubleLinkedList() {
this.size = 0;
}

public void addFirst(Node<T> newNode) {
if (size == 0) {
first = last = newNode;
} else {
newNode.setNext(first);
first.setPrevious(newNode);
first = newNode;
}
size++;
}

public void removeFirst() {
Node<T> nextNode = last.getNext();
last.setNext(null);
last = nextNode;
last.setPrevious(null);
}

public Node<T> getFirst() {
return first;
}

public Node<T> getLast() {
return last;
}

public void removeLast() {
Node<T> previousNode = last.getPrevious();
last.setPrevious(null);
last = previousNode;
last.setNext(null);
}

public void remove(Node<T> node) {
if (node == first) {
removeFirst();
} else if (node == last) {
removeLast();
} else {
Node<T> previousNode = node.getPrevious();
Node<T> nextNode = node.getNext();

node.setPrevious(null);
node.setNext(null);

previousNode.setNext(nextNode);
nextNode.setPrevious(previousNode);
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package lru;

import java.util.HashMap;
import java.util.Map;
import lib.DoubleLinkedList;
import lib.Node;

public class LRUCache {

private int capacity;
private Map<String, Node<String>> map;
private DoubleLinkedList<String> queue;

public LRUCache(int capacity) {
this.capacity = capacity;
this.queue = new DoubleLinkedList<>();
this.map = new HashMap<>();
}

public void put(String value) {
if (map.containsKey(value)) {
renew(value);
} else {
addNew(value);
}
}

private void addNew(String value) {
if (map.size() >= capacity) {
map.remove(queue.getLast().getData());
queue.removeLast();
}

Node<String> newNode = new Node<>(value);

queue.addFirst(newNode);
map.put(value, newNode);
}

private void renew(String value) {
Node<String> node = map.get(value);
queue.remove(node);
queue.addFirst(node);
}

public void displayQueue() {
Node<String> node = queue.getLast();
if (node != null) {
System.out.print(node.getData());
while (node.hasPrevious()) {
node = node.getPrevious();
System.out.print(" -> " + node.getData());
}
System.out.println();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import lru.LRUCache;

public class Main {
public static void main(String[] args) {
LRUCache cache = new LRUCache(3);

addToCache(cache, "chocolate");
addToCache(cache, "vanilla");
addToCache(cache, "strawberry");
addToCache(cache, "chocolate");
addToCache(cache, "pound");
}

private static void addToCache(LRUCache cache, String value) {
cache.put(value);
cache.displayQueue();
}
}

Kết quả chạy code

1
2
3
4
5
chocolate
chocolate -> vanilla
chocolate -> vanilla -> strawberry
vanilla -> strawberry -> chocolate
strawberry -> chocolate -> pound

Cám ơn các bạn đã đọc bài viết. Nếu có câu hỏi, hoặc các bạn biết những thuật toán hay được sử dụng trong coding interview, đừng ngần ngại chia sẻ nhé. 😁

Tham khảo: https://www.interviewcake.com/concept/java/lru-cache