思路
这题的关键是同时满足两个要求:
- O(1) 查找 :所以要用
HashMap。 - 维护最近使用顺序 :所以要用 双向链表。
只用 HashMap 可以 O(1) 找到 key,但不知道哪个是"最近最少使用"的;
只用链表可以维护顺序,但查找是 O(n)。
所以要把两者结合起来。
数据结构设计
维护一个 双向链表,链表头部表示最近使用的,尾部表示最久未使用的。
同时维护一个 HashMap<Integer, Node>,用来 O(1) 找到某个 key 对应的节点。
-
get(key):- 如果 key 不存在,返回
-1。 - 如果存在,把对应节点移到链表头部,表示最近使用过,然后返回它的值。
- 如果 key 不存在,返回
-
put(key, value):- 如果 key 已存在,更新node里的value值,并把节点移到链表头部。
- 如果 key 不存在:
- 先新建节点,插入链表头部,并放入 map。
- 再判断如果超过容量,就删除链表尾部节点,同时从 map 中移除。
movetohead:把当前链表中有的节点移到最前面 先removenode再addtohead
addtohead:把新建的节点插入到头部
removetail:移除tail.prev即最后一个节点
removenode:移除链表中指定节点
初始:head <-> tail
head <-> 1 <-> tail
假设当前是 head <-> A <-> tail,要插入新节点 X:注意顺序
执行前: head <-> A <-> tail
第1步 node.next = head.next X.next = A
第2步 node.prev = head X.prev = head
第3步 head.next.prev = node A.prev = X
第4步 head.next = node head.next = X
执行后: head <-> X <-> A <-> tail
removeNode(node)
假设当前是 head <-> A <-> B <-> C <-> tail,要删除 B:
执行前: A <-> B <-> C
第1步 A.next = C (B的前驱指向B的后继)
第2步 C.prev = A (B的后继指向B的前驱)
执行后: A <-> C (B被摘掉)
moveToHead(node)
就是先摘掉,再插到头部。比如:
head <-> A <-> B <-> C <-> tail
moveToHead(B):
先 removeNode(B): head <-> A <-> C <-> tail
再 addToHead(B): head <-> B <-> A <-> C <-> tail
removeTail()
直接取 tail.prev,就是最久未使用的节点,然后把它摘掉。
class LRUCache {
class Node{
int key;
int value;
Node prev;
Node next;
public Node(int key,int value ){
this.key=key;
this.value=value;
}
}
private final int capacity;
private final Map<Integer,Node> map;
private final Node head;
private final Node tail;
public LRUCache(int capacity) {
this.capacity=capacity;
this.map=new HashMap<>();
head=new Node(0,0);
tail=new Node(0,0);
head.next=tail;
tail.prev=head;
}
public int get(int key) {
if(!map.containsKey(key))
return -1;
Node node=map.get(key);
movetohead(node);
return node.value;
}
public void put(int key, int value) {
if(map.containsKey(key)){
Node node=map.get(key);
node.value=value;
movetohead(node);
return;
}
Node node=new Node(key,value);
map.put(key,node);
addtohead(node);
if(map.size()>capacity){
Node lastnode=tail.prev;
removetail();
map.remove(lastnode.key);
return;
}
}
private void addtohead(Node node){
head.next.prev=node;
node.prev=head;
node.next=head.next;
head.next=node;
}
private void removenode(Node node){
node.prev.next=node.next;
node.next.prev=node.prev;
}
private void movetohead(Node node){
removenode(node);
addtohead(node);
}
private Node removetail(){
Node lastnode=tail.prev;
removenode(lastnode);
return lastnode;
}
}
/**
-
Your LRUCache object will be instantiated and called as such:
-
LRUCache obj = new LRUCache(capacity);
-
int param_1 = obj.get(key);
-
obj.put(key,value);
*/
二叉树的中序遍历
中序遍历:左根右
思路:
1.递归 dfs(左子树) 根 dfs(右子树)
- 先递归遍历左子树
- 再访问当前节点(把值加入结果)
- 最后递归遍历右子树
2.用栈模拟递归stack
- 一路向左:从当前节点开始,不断往左走,把沿途节点全部压栈。这样栈顶就是最左边的节点(应该最先访问)。
- 弹栈访问:弹出栈顶节点,把它的值加入结果(这就是"根"的位置)。
- 转向右边 :把当前指针移到这个节点的右子树,然后重复第1步。
栈思路:只要当前节点不为空,或者栈也不为空,就先一路把左节点push进栈,当某一时刻为null时,将栈顶元素出栈,并且加入结果数组,然后指针转向右子树,重复循环,继续一路把左节点push进栈
保证了左 根 右
class Solution {
public List inorderTraversal(TreeNode root) {
ArrayList res=new ArrayList<>();
ArrayDeque stack=new ArrayDeque<>();
TreeNode cur=root;
while(cur!=null||!stack.isEmpty()){
while(cur!=null){
stack.push(cur);
cur=cur.left;
}
cur=stack.pop();
res.add(cur.val);
cur=cur.right;
}
return res;
}
}