仅用单链表、单链表加hashMap、双向链表加hashMap、LinkedHashMap实现LRU

leetCode题目表述 题目理解会有一个误区:这里的“最近”指是最近时间以内是否访问过,而非距离最近。

解体思路:
首先涉及元素频繁的增删,使用链表。根据题意,可维护一个队列,将最近访问过的元素(get、put操作)放在队列尾部,相反最久时间没访问的元素的位置则都在队列头。
get操作:如果能找到该元素(使用hashmap寻找或遍历链表寻找),则将其移动到队列尾部,返回其value值。否则返回-1;
put操作:如果能找到该元素(使用hashmap寻找或遍历链表寻找),则将其移动到队列尾部,并更新value值即可。
否则将其添加到队列尾部。此时判断如果size小于容量capacity则正常退出即可。如果size大于容量capacity则删除队列的头元素,即删除最久未使用的元素。

一.仅用单链表

class LRUCache {
    Node head;
    int capacity;
    int length;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.length = 0;
    }

    public int get(int key) {
        Node findNode = findNodeByKey(key);
        if(findNode!=null) {
            //从原来位置删除
            deleteData(key);
            //从头添加
            addAtHead(key,findNode.value);
            return findNode.value;
        }
        return -1;
    }

    public void put(int key, int value) {
        //已经存在key,则更新
        Node findNode = findNodeByKey(key);
        if(findNode!=null) {
            //从原来位置删除
            deleteData(key);
            //从头添加
            addAtHead(key,value);
        } else {//key不存在,则添加
            //未装满添加
            if(length<capacity){
                //从头添加
                addAtHead(key,value);
            } else {//装满添加
                //删除最后一个元素
                deleteAtTail();
                //从头添加
                addAtHead(key,value);
            }
        }

    }

    //查找目标节点
    public Node findNodeByKey(int key){
        if(head == null)
            return null;
        Node cur = head;
        while(cur!=null) {
            if(key==cur.key){
                return cur;
            }
            cur = cur.next;
        }
        return null;
    }

    //从头添加
    public void addAtHead(int key,int value){
        Node newNode = new Node(key,value);
        if(head == null){
            head = newNode;
        } else {
            newNode.next = head;
            head = newNode;
        }
        length++;
    }

    //删除最后一个元素
    public void deleteAtTail(){
        Node cur = head;
        if(cur == null) {
            return ;
        }
        Node next=cur.next;
        if(next==null){
            head=null;
            length--;
            return;
        }

        while(next.next!=null)
        {
            cur=next;
            next=next.next;
        }
        cur.next=null;

        length--;
    }

    //删除指定key
    public void deleteData(int key){
        if(key==head.key){
            head = head.next;
            length--;
            return;
        } else {
            Node cur = head;
            Node pre = null;
            while(cur!=null){
                if(key==cur.key){
                    pre.next = cur.next;
                    cur.next = null;
                    length--;
                    return;
                }
                pre = cur;
                cur = cur.next;
            }
        }
    }

    class Node {
        int key;
        int value;
        Node next;
        Node(int key,int value){
            this.key = key;
            this.value = value;
            next = null;
        }
    }
}

二.单链表加hashMap

这里为了方便删除操作,在map中不存当前节点,而是存当前节点的前驱。 另外使用了哨兵(带头节点)

public class LRUCache{

    private class ListNode {
        int key, val;
        ListNode next;

        public ListNode(int key, int val) {
            this.key = key;
            this.val = val;
            this.next = null;
        }
    }

    private int capacity;
    private Map<Integer, ListNode> map;     //key-> node.pre
    private ListNode head;  //dummy
    private ListNode tail;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new HashMap<>();
        head = new ListNode(-1, -1);
        tail = head;
    }

    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        //map中存放的是要找的节点的前驱
        ListNode pre = map.get(key);
        ListNode cur = pre.next;

        //把当前节点删掉并移到尾部
        if (cur != tail) {
            pre.next = cur.next;
            map.put(cur.next.key, pre); //更新它后面node的前驱
            map.put(cur.key, tail);
            moveToTail(cur);
        }
        return cur.val;
    }

    public void put(int key, int value) {
        if (get(key) != -1) {
            map.get(key).next.val = value;
            return;
        }
        //不存在就new一个
        ListNode node = new ListNode(key, value);
        map.put(key, tail); //当前node的pre是tail
        moveToTail(node);

        if (map.size() > capacity) {
            map.remove(head.next.key);
            map.put(head.next.next.key, head);
            head.next = head.next.next;
        }
    }

    private void moveToTail(ListNode node) {
        node.next = null;
        tail.next = node;
        tail = tail.next;
    }
}

作者:jerry_nju
链接:https://leetcode-cn.com/problems/lru-cache-lcci/solution/linkedhashmap-shuang-lian-biao-hashmap-dan-lian-2/
来源:力扣(LeetCode)

三.双向链表加hashMap

考虑删除操作,要把当前节点的前一个节点的指针的改变,获取它前一个节点,方便的数据结构就是 双向链表,这里使用了哨兵(头尾都使用了空节点)

class LRUCache {
    int capacity;
    Map<Integer,Node> map = new HashMap<>();
    Node head ;
    Node tail;
    public LRUCache(int capacity) {
        this.capacity = capacity;
        head = new Node(-1, -1);
        tail = new Node(-1, -1);
        head.next=tail;
        tail.pre=head;
    }

    public int get(int key) {
        //hashmap中是否有这个元素,如果没有直接返回-1
        if(!map.containsKey(key))
            return-1;
        //如果有,则将这个元素放到链表的尾部
        Node cur = map.get(key);
        //删除这个元素
        cur.pre.next = cur.next;
        cur.next.pre = cur.pre;
        //放到链表的尾部
        moveToTail(cur);
        return cur.value;
    }

    public void put(int key, int value) {
        //直接调用这边的get方法,如果存在,它会在get内部被移动到尾巴,不用再移动一遍,直接修改值即可
        if(get(key)!=-1) {
            map.get(key).value = value;
            return;
        }
        //如果不存在则添加到尾部
        Node newNode = new Node(key,value);
        map.put(key,newNode);
        moveToTail(newNode);
        //如果此时超出容量,则从头删去一个旧元素
        if(map.size()>capacity) {
            map.remove(head.next.key);
            head.next = head.next.next;
            head.next.pre = head;
        }

    }

    private  void moveToTail(Node cur) {
        tail.pre.next = cur;
        cur.next = tail;
        cur.pre = tail.pre;
        tail.pre = cur;
    }

    class Node {
        int key;
        int value;
        Node pre;
        Node next;
        Node(int key,int value){
            this.key = key;
            this.value = value;
            this.pre = null;
            this.next = null;
        }
    }
}

四.LinkedHashMap

使用LinkedHashMap的accessOrder模式

public class LRUCache
{
    int capacity;
    Map<Integer, Integer> map;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        map = new LinkedHashMap<>((int)((float)capacity / 0.75f + 1.0f), 0.75f, true);
    }

    public int get(int key)
    {
        Integer value = map.get(key);
        if (value == null)
            return -1;
        return value;
    }

    public void put(int key, int value) {
        map.put(key, value);
        if (map.size() > capacity) {
            //超出capacity,删除最久没用的,利用迭代器,删除第一个
            map.remove(map.entrySet().iterator().next().getKey());
        }
    }

}

总结:解题使用的数据结构可以是 LinkedList(底层是双向链表)+ HashMap,而实际上可以直接用LinkedHashMap更为方便。看面试官要求是啥了。

posted @ 2020-08-27 10:48  两行法桐  阅读(72)  评论(0)    收藏  举报