Skip to content

Commit 1a63d23

Browse files
committed
146
1 parent 1ac6259 commit 1a63d23

4 files changed

+242
-4
lines changed

SUMMARY.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@
102102
* [98. Validate Binary Search Tree](leetCode-98-Validate-Binary-Search-Tree.md)
103103
* [99. Recover Binary Search Tree](leetcode-99-Recover-Binary-Search-Tree.md)
104104
* [100. Same Tree](leetcode-100-Same-Tree.md)
105-
* [101 题到 145题](leetcode-101-200.md)
105+
* [101 题到 146题](leetcode-101-200.md)
106106
* [101. Symmetric Tree](leetcode-101-Symmetric-Tree.md)
107107
* [102. Binary Tree Level Order Traversal](leetcode-102-Binary-Tree-Level-Order-Traversal.md)
108108
* [103. Binary Tree Zigzag Level Order Traversal](leetcode-103-Binary-Tree-Zigzag-Level-Order-Traversal.md)
@@ -147,4 +147,5 @@
147147
* [142. Linked List Cycle II](leetcode-142-Linked-List-CycleII.md)
148148
* [143. Reorder List](leetcode-143-Reorder-List.md)
149149
* [144. Binary Tree Preorder Traversal](leetcode-144-Binary-Tree-Preorder-Traversal.md)
150-
* [145*. Binary Tree Postorder Traversal](leetcode-145-Binary-Tree-Postorder-Traversal.md)
150+
* [145*. Binary Tree Postorder Traversal](leetcode-145-Binary-Tree-Postorder-Traversal.md)
151+
* [146. LRU Cache](leetcode-146-LRU-Cache.md)

leetcode-101-200.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,6 @@
8686

8787
<a href="leetcode-144-Binary-Tree-Preorder-Traversal.html">144. Binary Tree Preorder Traversal</a>
8888

89-
<a href="leetcode-145-Binary-Tree-Postorder-Traversal.html">145. Binary Tree Postorder Traversal</a>
89+
<a href="leetcode-145-Binary-Tree-Postorder-Traversal.html">145. Binary Tree Postorder Traversal</a>
90+
91+
<a href="leetcode-146-LRU-Cache.html">146. LRU Cache</a>

leetcode-145-Binary-Tree-Postorder-Traversal.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ public List<Integer> postorderTraversal(TreeNode root) {
346346

347347
所以我们可以用 `LinkedList` , 这样倒置链表就只需要遍历一遍,也不需要额外的空间了。
348348

349-
更近一步,我们在调用 `list.add` 的时候,其实可以之间 `list.addFirst` ,每次都插入到链表头,这样做的话,最后也不需要逆转链表了。
349+
更近一步,我们在调用 `list.add` 的时候,其实可以直接 `list.addFirst` ,每次都插入到链表头,这样做的话,最后也不需要逆转链表了。
350350

351351
# 解法四 Morris Traversal
352352

leetcode-146-LRU-Cache.md

+235
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
# 题目描述(中等难度)
2+
3+
![](https://windliang.oss-cn-beijing.aliyuncs.com/146.jpg)
4+
5+
`LRU` 缓存。存储空间有限,当存满的时候的一种淘汰策略。`LRU` 选择删除最远一次操作过的元素,操作包括`get` 或者 `put` 。换句话讲,最开始 `put` 进去的如果没有进行过 `get`,那存满的时候就先删它。
6+
7+
# 思路分析
8+
9+
看到 `O(1)` 的空间复杂度首先想到的就是用 `HashMap` 去存储。
10+
11+
之后的问题就是怎么实现,当存满的时候删除最远一次操作过的元素。
12+
13+
可以用一个链表,每 `put` 一个 元素,就把它加到链表尾部。如果 `get` 某个元素,就把这个元素移动到链表尾部。当存满的时候,就把链表头的元素删除。
14+
15+
接下来还有一个问题就是,移动某个元素的时候,我们可以通过 `HashMap` 直接得到这个元素,但对于链表,如果想移动一个元素,肯定需要知道它的前一个节点才能操作。
16+
17+
而找到前一个元素,最直接的方法就是遍历一遍,但是这就使得算法的时间复杂度就不再是 `O(1)` 了。
18+
19+
另一种思路,就是使用双向链表,这样就可以直接得到它的前一个元素,从而实现移动操作。
20+
21+
综上,`HashMap` 加上双向链表即可解这道题了。
22+
23+
# 解法一
24+
25+
有了上边的思路,接下来就是实现上的细节了,最后的参考图如下。
26+
27+
![](https://windliang.oss-cn-beijing.aliyuncs.com/146_2.jpg)
28+
29+
首先定义节点。
30+
31+
```java
32+
class MyNode {
33+
Object key;
34+
Object value;
35+
MyNode prev = null;
36+
MyNode next = null;
37+
MyNode(Object k, Object v) {
38+
key = k;
39+
value = v;
40+
}
41+
}
42+
```
43+
44+
定义双向链表类。
45+
46+
这里用了一个 `dummyHead` ,也就是哨兵节点,不存数据,可以把链表头结点等效于其他的节点,从而简化一些操作。
47+
48+
```java
49+
class DoubleLinkedList {
50+
private MyNode dummyHead = new MyNode(null, null); // 头节点
51+
private MyNode tail = dummyHead;
52+
//添加节点到末尾
53+
public void add(MyNode myNode) {
54+
tail.next = myNode;
55+
myNode.prev = tail;
56+
tail = myNode;
57+
}
58+
59+
//得到头结点
60+
public MyNode getHead() {
61+
return dummyHead.next;
62+
}
63+
64+
//移除当前节点
65+
public void removeMyNode(MyNode myNode) {
66+
myNode.prev.next = myNode.next;
67+
//判断删除的是否是尾节点
68+
if (myNode.next != null) {
69+
myNode.next.prev = myNode.prev;
70+
} else {
71+
tail = myNode.prev;
72+
}
73+
//全部指向 null
74+
myNode.prev = null;
75+
myNode.next = null;
76+
}
77+
78+
//移动当前节点到末尾
79+
public void moveToTail(MyNode myNode) {
80+
removeMyNode(myNode);
81+
add(myNode);
82+
}
83+
}
84+
```
85+
86+
接下来就是我们的 `LRU` 类。
87+
88+
```java
89+
public class LRUCache {
90+
private int capacity = 0;
91+
private HashMap<Integer, MyNode> map = new HashMap<>();
92+
private DoubleLinkedList list = new DoubleLinkedList();
93+
94+
public LRUCache(int capacity) {
95+
this.capacity = capacity;
96+
}
97+
98+
//get 的同时要把当前节点移动到末尾
99+
public int get(int key) {
100+
if (map.containsKey(key)) {
101+
MyNode myNode = map.get(key);
102+
list.moveToTail(myNode);
103+
return (int) myNode.value;
104+
} else {
105+
return -1;
106+
}
107+
}
108+
109+
//对于之前存在的节点单独考虑
110+
public void put(int key, int value) {
111+
if (map.containsKey(key)) {
112+
MyNode myNode = map.get(key);
113+
myNode.value = value;
114+
list.moveToTail(myNode);
115+
} else {
116+
//判断是否存满
117+
if (map.size() == capacity) {
118+
//从 map 和 list 中都删除头结点
119+
MyNode head = list.getHead();
120+
map.remove((int) head.key);
121+
list.removeMyNode(head);
122+
//插入当前元素
123+
MyNode myNode = new MyNode(key, value);
124+
list.add(myNode);
125+
map.put(key, myNode);
126+
} else {
127+
MyNode myNode = new MyNode(key, value);
128+
list.add(myNode);
129+
map.put(key, myNode);
130+
}
131+
}
132+
}
133+
}
134+
```
135+
136+
接下来把上边的代码放在一起就可以了。
137+
138+
```java
139+
class MyNode {
140+
Object key;
141+
Object value;
142+
MyNode prev = null;
143+
MyNode next = null;
144+
MyNode(Object k, Object v) {
145+
key = k;
146+
value = v;
147+
}
148+
}
149+
150+
class DoubleLinkedList {
151+
private MyNode dummyHead = new MyNode(null, null); // 头节点
152+
private MyNode tail = dummyHead;
153+
//添加节点到末尾
154+
public void add(MyNode myNode) {
155+
tail.next = myNode;
156+
myNode.prev = tail;
157+
tail = myNode;
158+
}
159+
160+
//得到头结点
161+
public MyNode getHead() {
162+
return dummyHead.next;
163+
}
164+
165+
//移除当前节点
166+
public void removeMyNode(MyNode myNode) {
167+
myNode.prev.next = myNode.next;
168+
//判断删除的是否是尾节点
169+
if (myNode.next != null) {
170+
myNode.next.prev = myNode.prev;
171+
} else {
172+
tail = myNode.prev;
173+
}
174+
//全部指向 null
175+
myNode.prev = null;
176+
myNode.next = null;
177+
}
178+
179+
//移动当前节点到末尾
180+
public void moveToTail(MyNode myNode) {
181+
removeMyNode(myNode);
182+
add(myNode);
183+
}
184+
}
185+
186+
public class LRUCache {
187+
private int capacity = 0;
188+
private HashMap<Integer, MyNode> map = new HashMap<>();
189+
private DoubleLinkedList list = new DoubleLinkedList();
190+
191+
public LRUCache(int capacity) {
192+
this.capacity = capacity;
193+
}
194+
195+
//get 的同时要把当前节点移动到末尾
196+
public int get(int key) {
197+
if (map.containsKey(key)) {
198+
MyNode myNode = map.get(key);
199+
list.moveToTail(myNode);
200+
return (int) myNode.value;
201+
} else {
202+
return -1;
203+
}
204+
}
205+
206+
//对于之前存在的节点单独考虑
207+
public void put(int key, int value) {
208+
if (map.containsKey(key)) {
209+
MyNode myNode = map.get(key);
210+
myNode.value = value;
211+
list.moveToTail(myNode);
212+
} else {
213+
//判断是否存满
214+
if (map.size() == capacity) {
215+
//从 map 和 list 中都删除头结点
216+
MyNode head = list.getHead();
217+
map.remove((int) head.key);
218+
list.removeMyNode(head);
219+
//插入当前元素
220+
MyNode myNode = new MyNode(key, value);
221+
list.add(myNode);
222+
map.put(key, myNode);
223+
} else {
224+
MyNode myNode = new MyNode(key, value);
225+
list.add(myNode);
226+
map.put(key, myNode);
227+
}
228+
}
229+
}
230+
}
231+
```
232+
233+
#
234+
235+
最关键的其实就是双向链表的应用了,想到这个点其他的话就水到渠成了。

0 commit comments

Comments
 (0)