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代码的话就会发现LinkedListNode类是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~!