LRUCache之问
LRUCache! 干就完了
今天面了拼多多,LRUCache
再次出现在了我的面前,面对这个半年以来面对最多的面试代码问题,我再次给出我的解决方案:
1、链表
使用链表作为最主要的数据结构,将数据按照先后顺序在链表尾部插入,直到满了开始从头减去链表,时间复杂度分析分别为:
get | O(n) |
---|---|
add | O(n) |
remove | O(n) |
使用单向链表,get需要找到我们要的cache
所在位置,因此时间复杂度为O(n),add由于需要先判断是否已经存在,所以需要调用get方法,在满的时候需要remove,所以虽然删除时间复杂度为O(1)
,整体时间复杂度为O(n)
,而remove
从头部删除时间复杂度为O(1)但是在中间的时候需要做get
操作,时间复杂度依旧为O(n)
。使用双向链表可以降低add()
时间复杂度,但是整体来说时间复杂度在O(n)
.
代码实现如下:
class LRUCacheLinkedList<K,V>{ int capcity; HashMap<K,V> data; LinkedList<K> list; public LRUCacheLinkedList(int capcity){ data = new HashMap<>(capcity); list = new LinkedList<>(); this.capcity = capcity; } public V get(K key){ if(data.containsKey(key)) return data.get(key); else return null; } public Boolean add(K key, V value){ if(data.containsKey(key)){ data.put(key,value); list.remove(list.indexOf(key)); list.add(key); } else { if(data.size() < capcity){ list.add(key); } else { list.remove(0); list.add(key); } } return true; } public V remove(K key){ list.remove(list.indexOf(key)); V res = data.getOrDefault(key, null); data.remove(key); return res; } }
这里面主要的时间复杂度在于在链表get方法,时间复杂度为O(1);
2、HashMap + LinkedList
使用双向链表保存数据,使用HashMap
保存数据在LinkedList
中的位置,使用HashMap
保存key
值和key
值对应的DLinkNode
时间复杂度如下:
get | O(1) |
---|---|
add | O(1) |
remove | O(1) |
代码实现如下,有的小朋友可能会问了:显得叔叔显得叔叔为什么要手写DlinkNode
,java的LinkedList
不就是双向链表吗?
这个小朋友问的就非常好,为什么要手写呢?因为如果看过LinkedList
代码的话就会发现LinkedList
的Node
类是private static
修饰的,我想拿也拿不到,所以就会自己写。
class LRUCacheHashMap<K,V> { /*为什么不用LinkeList的Node类,非要自己写? 因为LinkeList的Node类是private static的,所以要是想要用获得Node的地址就得自己实现 * */ class DLinkedNode { V value; DLinkedNode prev; DLinkedNode next; public DLinkedNode() {} public DLinkedNode( V _value) {value = _value;} } private Map<K, DLinkedNode> cache = new HashMap<>(); private int size; private int capacity; private DLinkedNode head, tail; public LRUCacheHashMap(int capacity) { this.size = 0; this.capacity = capacity; // 使用伪头部和伪尾部节点 head = new DLinkedNode(); tail = new DLinkedNode(); head.next = tail; tail.prev = head; } public V get(int key) { DLinkedNode node = cache.get(key); if (node == null) { return null; } // 如果 key 存在,先通过哈希表定位,再移到头部 moveToHead(node); return node.value; } public void put(K key, V value) { DLinkedNode node = cache.get(key); if (node == null) { // 如果 key 不存在,创建一个新的节点 DLinkedNode newNode = new DLinkedNode( value); // 添加进哈希表 cache.put(key, newNode); // 添加至双向链表的头部 addToHead(newNode); ++size; if (size > capacity) { // 如果超出容量,删除双向链表的尾部节点 DLinkedNode tail = removeTail(); // 删除哈希表中对应的项 cache.remove(key); --size; } } else { // 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部 node.value = value; moveToHead(node); } } private void addToHead(DLinkedNode node) { node.prev = head; node.next = head.next; head.next.prev = node; head.next = node; } private void removeNode(DLinkedNode node) { node.prev.next = node.next; node.next.prev = node.prev; } private void moveToHead(DLinkedNode node) { removeNode(node); addToHead(node); } private DLinkedNode removeTail() { DLinkedNode res = tail.prev; removeNode(res); return res; } }
但是记住碰到面试官问LRUCache不要直接写奥,要先写一个O(n)的,然后让他启发你,你再把O(1)的写出来,秋招小心机get~!