场景算法

LeetCode 面试题 16.25. LRU缓存

设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。

获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。

示例:

LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得密钥 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得密钥 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4

思路:使用双向链表实现,每个节点包括k,v数据以及前驱、后继节点。此外,链表中还有虚拟节点dummy,让每个节点的pre和next均不为空,简化操作。
为什么要保存key?
在删除链表末尾节点时,需要删除哈希表中的记录,需要查找末尾节点的key。

实现:

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
class LRUCache {
private static class Node {
int key, value;
Node pre, next;

Node(int k, int v) {
key = k;
value = v;
}
}

private final int capacity;
private final Node dummy = new Node(0,0);
private final Map<Integer,Node> keyToNode = new HashMap<>();

public LRUCache(int capacity) {
this.capacity = capacity;
dummy.pre = dummy;
dummy.next = dummy;
}

public int get(int key) {
Node node = getNode(key);
return node != null ? node.value : -1;
}

public void put(int key, int value) {
Node node = getNode(key);
if (node != null) {
node.value = value;
return;
}
node = new Node(key, value);
keyToNode.put(key,node);
pushFront(node);
if (keyToNode.size() > capacity) {
Node backNode = dummy.pre;
keyToNode.remove(backNode.key);
remove(backNode);
}
}

private Node getNode(int key) {
if (! keyToNode.containsKey(key)) return null;
Node node = keyToNode.get(key);
remove(node);
pushFront(node);
return node;
}

private void remove(Node x) {
x.pre.next = x.next;
x.next.pre = x.pre;
}

private void pushFront(Node x) {
x.pre = dummy;
x.next = dummy.next;
x.pre.next = x;
x.next.pre = x;
}
}

手写单例模式的实现

1.懒汉式

懒汉式单例是在第一次使用时创建实例,但这种方式在多线程环境下不安全,可能导致多个实例的创建。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
// 静态变量,保存唯一的实例
private static Singleton instance;

// 私有化构造函数,避免外部直接创建实例
private Singleton() {}

// 提供公共的静态方法来获取实例
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // 这里是懒加载
}
return instance;
}
}

1.1懒汉式的线程安全模式

通过在 getInstance() 方法加锁来实现线程安全,但由于每次都加锁,可能会影响性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
// 静态变量,保存唯一的实例
private static Singleton instance;

// 私有化构造函数,避免外部直接创建实例
private Singleton() {}

// 提供公共的静态方法来获取实例
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}

1.2双重检查锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Singleton {
// 使用 volatile 确保可见性
private static volatile Singleton instance;

// 私有化构造函数,避免外部直接创建实例
private Singleton() {}

// 提供公共的静态方法来获取实例
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

1.3静态内部类

这种方式是线程安全的,并且延迟加载,推荐使用。JVM 确保了静态内部类只会加载一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Singleton {
// 静态内部类
private static class SingletonHelper {
// 静态变量,保存唯一的实例
private static final Singleton instance = new Singleton();
}

// 私有化构造函数,避免外部直接创建实例
private Singleton() {}

// 提供公共的静态方法来获取实例
public static Singleton getInstance() {
return SingletonHelper.instance;
}
}

2.饿汉式

这种方式在类加载时就创建实例,不会等到需要的时候才创建。

1
2
3
4
5
6
7
8
9
10
11
12
public class Singleton {
// 静态变量,保存唯一的实例
private static final Singleton instance = new Singleton();

// 私有化构造函数,避免外部直接创建实例
private Singleton() {}

// 提供公共的静态方法来获取实例
public static Singleton getInstance() {
return instance;
}
}

三个线程循环打印斐波那契数列

实现:

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
public class FibonacciPrinter {
private static final Object lock = new Object(); // 用于线程间同步

private static int fib1 = 0; // 斐波那契数列的第一个数字
private static int fib2 = 1; // 斐波那契数列的第二个数字
private static int count = 0; // 控制打印的次数

public static void main(String[] args) {
// 创建三个线程
Thread thread1 = new Thread(new FibonacciTask(1));
Thread thread2 = new Thread(new FibonacciTask(2));
Thread thread3 = new Thread(new FibonacciTask(3));

// 启动线程
thread1.start();
thread2.start();
thread3.start();
}

// 斐波那契数列打印任务
static class FibonacciTask implements Runnable {
private int threadId;

public FibonacciTask(int threadId) {
this.threadId = threadId;
}

@Override
public void run() {
while (count < 30) { // 打印30个斐波那契数列
synchronized (lock) {
// 判断当前线程是否轮到它打印
if (count % 3 == threadId - 1) {
int nextFib = fib1 + fib2;
System.out.println("Thread " + threadId + " prints: " + fib1);
fib1 = fib2;
fib2 = nextFib;
count++;
lock.notifyAll(); // 唤醒其他线程
} else {
try {
lock.wait(); // 当前线程等待
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
}
}

场景算法
http://bloomivy.github.io/2025/01/18/场景算法/
作者
Bloom
发布于
2025年1月18日
许可协议