leetcode——LRU缓存

题目:设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能

  • set(key, value):将记录(key, value)插入该结构
  • get(key):返回key对应的value值

[要求]

  1. set和get方法的时间复杂度为O(1)
  2. 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
  3. 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。

 若opt=1,接下来两个整数x, y,表示set(x, y)
        若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
        对于每个操作2,输出一个答案

方法一:使用了LinkedHashMap

我们知道了要将最近使用的节点作为最长使用的,当缓存超过k的时候,移除最不常用的。这就是按照访问顺序来访问链表,这个就让我们想到了LinkedHashMap。为了有顺序的访问LinkedHashMap在底层为了双向链表,保证了元素迭代的顺序。该顺序可以是插入顺序和访问顺序。底层数据结构是这个样子的:使用一个双向链表了记录访问顺序的。

public class Solution {

    public static void main(String[] args) {
        int[][] operators = {{1, 1, 1}, {1, 2, 2}, {1, 3, 2}, {2, 1}, {1, 4, 4}, {2, 2}};
        Solution solution = new Solution();
        int[] ints = solution.LRU(operators, 3);
        System.out.println(Arrays.toString(ints));
    }

    public int[] LRU (int[][] operators, int k) {
        int len = (int) Arrays.stream(operators).filter(operator -> operator[0] == 2).count();
        int[] ret=new int[len];
        LRUCache lru=new LRUCache(k);
        int index=0;
        for(int[] operator:operators)
        {
            if(operator[0]==1)
            {

                lru.save(operator[1],operator[2]);


            }else{
                if(lru.contains(operator[1])) {
                   ret[index]= lru.getOne(operator[1]);
                   index++;
                }
                else{
                    ret[index]=-1;
                    index++;
                }

            }
        }
        return ret;
    }


    final class LRUCache extends LinkedHashMap<Integer, Integer> {
        //节点个数

        private int limit;
        //构造函数
        public LRUCache(int limit)
        {
            super(limit,0.75f,true);
            this.limit=limit;
        }

        //添加节点:实际内部就是LinkedHashMap的put
        public void save(Integer key,Integer val)
        {
             put(key,val);
        }

        //获得节点:实际内部就是LinkedHashMap的get
        public Integer getOne(Integer key)
        {
            return get(key);
        }
        public boolean contains(Integer key)
        {
            return containsKey(key);
        }

        //这个方法是必须要重写的,为了判断存在的键值对的个数是否大于限制
        @Override
        protected boolean removeEldestEntry(Map.Entry<Integer, Integer> eldest) {
            return size()>limit;
        }


    }

方法二:自己写底层

LinkedHashMap源码解析中,知道了底层数据结构是桶数组+双向链表和红黑树。在LRU中可以不使用红黑树,只设置这个双向链表和桶数组以及其中的链表。

在set的时候,要记得按照访问顺序设置最常使用的节点(就是将这个节点放在双向链表的尾部),这样双向链表的头结点就是不常使用的节点,在链表节点个数大于K的时候,就可以删除这个节点。

在get的时候,也是要按照访问顺序设置最常使用的节点。

import java.util.*;


public class Solution {
    /**
     * lru design
     * @param operators int整型二维数组 the ops
     * @param k int整型 the k
     * @return int整型一维数组
     */
     public int[] LRU (int[][] operators, int k) {
         int resultLength=0;
         for(int[] operator:operators)
         {
             if(operator[0]==2){
                 resultLength++;
             }
         }
         int[] results=new int [resultLength];
         int index=0;
         LruCache cache=new LruCache(k);
        
         for(int[] operator:operators)
         {
             switch(operator[0])
             {
                 case 1:
                     cache.set(operator[1],operator[2]);
                     break;
                 case 2:
                     Integer value=cache.get(operator[1]);
                     results[index++]=value == null?-1:value;
             }
         }
         return results;
    }
    
    
    //之前使用的是LinkedHashMap,底层已经实现好了,但是时间和空间就比较大了
    class LruCache{
        private final int capacity;
        private final Node[] hashTable;
        private int nodeCount;
        private Node lruHead;
        private Node lruTail;
        //要删除的节点
        private Node eliminateNode;
        
        //构造函数
        LruCache(int capacity)
        {
            if(capacity<1)
            {
                throw new IllegalArgumentException();
            }
            this.capacity=capacity;
            this.hashTable=new Node[(int)(capacity/0.75)];
        }
        
        //set方法
        public Integer set(int key,int value)
        {
            //根据Key获得位置
            int index=hashIndex(key);
            Node node=hashTable[index];
            //这个位置有没有节点
            while(node!=null && node.key!=key)
            {
                node=node.hashLink;
            }
            
            //找到了一个key相同的
            if(node!=null)
            {
                int oldValue=node.value;
                node.value=value;
                touch(node);
                return oldValue;
            }
            
            
            if(eliminateNode==null)
            {
                node=new Node();
            }else{
                node=eliminateNode;
                eliminateNode=null;
            }
            node.key=key;
            node.value=value;
            node.hashLink=hashTable[index];
            hashTable[index]=node;
            if(nodeCount++==0)
            {
                lruHead=node;
                lruTail=node;
            }
            else{
                node.lruPrev=lruTail;
                lruTail.lruNext=node;
                lruTail=node;
            }
            if(nodeCount>capacity)
            {
                eliminate();
            }
            return null;
        }
        
        public Integer get(int key)
        {
            int index=hashIndex(key);
            Node node=hashTable[index];
            while(node!=null && node.key!=key)
            {
                node=node.hashLink;
            }
            if(node==null)
            {
                return null;
            }
            touch(node);
            return node.value;
            
        }
        
        
        //就是按照访问顺序将节点放到了链表的后面
        private void touch(Node node){
            //是尾结点,直接返回
            if(node.lruNext==null)
            {
                return;
            }
            //头结点
            if(node.lruPrev==null)
            {
                lruHead=node.lruNext;
                lruHead.lruPrev=null;
            }else{
                node.lruPrev.lruNext=node.lruNext;
                node.lruNext.lruPrev=node.lruPrev;
            }
            node.lruNext=null;
            node.lruPrev=lruTail;
            lruTail.lruNext=node;
            lruTail=node;
        }
        
        //删除节点
        private void eliminate()
        {
           Node node=lruHead;
            if(node==null)
            {
                return;
            }
            //取下第一个节点
            lruHead=node.lruNext;
            lruHead.lruPrev=null;
            node.lruNext=null;
            // node 节点就是要删除的节点,这个将在双向链表中删除了节点
            
            //下来需要在桶数组中删除
            //获得位置
            int index=hashIndex(node.key);
            Node cur=hashTable[index];
            Node last=null;
            while(cur!=null && cur!=node)
            {
                last=cur;
                cur=cur.hashLink;
            }
            //找到要删除的尾结点,cur要删除的节点,last为前一个节点
            //说明node是这个桶数组的第一个节点
            if(last==null)
            {
                hashTable[index]=node.hashLink;
            }
            else{
                last.hashLink=node.hashLink;
            }
            node.hashLink=null;
            eliminateNode=node;
            nodeCount--;
        }
        
        
        private int hashIndex(int key)
        {
           return (key < 0 ? (key == Integer.MIN_VALUE ? 0 : -key) : key) % hashTable.length;
        }
        
       
    }
      
    static class Node{
        int key;
        int value;
        Node hashLink;
        Node lruPrev;
        Node lruNext;
    }


  
}

方法三:HashMap+链表

       上面使用数组比较麻烦,下面使用HashMap

public class LRUCache {
   //节点 
   class Node{
       int key;
       int value;
       Node pre;
       Node next;
       public Node(){}
       public Node(int _key,int _value)
       {
           key=_key;
           value=_value;
       }
   }

   int size;
   int capacity;
   Map<Integer,Node> map=new HashMap<>();
   Node head;
   Node tail;

   public LRUCache(int capacity)
   {
       this.capacity=capacity;
       this.size=0;
       head=new Node();
       tail=new Node();
       head.next=tail;
       tail.pre=head;
   }

   public int get(int key)
   {
       Node cur=map.get(key);
       if(cur==null)
       {
           return -1;
       }
       //移动到链表的头部
       moveToHead(cur);
       return cur.value;
   }

   public void moveToHead(Node node)
   {
       removeNode(node);
       moveHead(node);
   }

   public void removeNode(Node node)
   {
       node.pre.next=node.next;
       node.next.pre=node.pre;
   }

   public void moveHead(Node node)
   {
       node.pre=head;
       node.next=head.next;
       head.next.pre=node;
       head.next=node;
   }


   public void put(int key,int value)
   {
       Node cur=map.get(key);
       if(cur==null)
       {
           Node node=new Node(key,value);
           map.put(key,node);
           moveHead(node);
           ++size;
           if(size>capacity)
           {
               //删除链表末尾
               Node delete=reomoveTail();
               map.remove(delete.key);
               --size;

           }

       }else
       {
           cur.value=value;
           moveToHead(cur);
       }
       
   }


   public Node reomoveTail()
   {
       //这里的tail是一个NUll节点
       Node tailP=tail.pre;
       removeNode(tailP);
       return tailP;
   }

}

/**
 * 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);
 */

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值