● 请你讲讲LRU算法的实现原理?
考察点:LRU算法
参考回答:
①LRU(Least recently used,最近最少使用)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也很高”,反过来说“如果数据最近这段时间一直都没有访问,那么将来被访问的概率也会很低”,两种理解是一样的;常用于页面置换算法,为虚拟页式存储管理服务。
②达到这样一种情形的算法是最理想的:每次调换出的页面是所有内存页面中最迟将被使用的;这可以最大限度的推迟页面调换,这种算法,被称为理想页面置换算法。可惜的是,这种算法是无法实现的。
为了尽量减少与理想算法的差距,产生了各种精妙的算法,最近最少使用页面置换算法便是其中一个。LRU 算法的提出,是基于这样一个事实:在前面几条指令中使用频繁的页面很可能在后面的几条指令中频繁使用。反过来说,已经很久没有使用的页面很可能在未来较长的一段时间内不会被用到 。这个,就是著名的局部性原理——比内存速度还要快的cache,也是基于同样的原理运行的。因此,我们只需要在每次调换时,找到最近最少使用的那个页面调出内存。
算法实现的关键
命中率:
当存在热点数据时,LRU的效率很好,但偶发性的、周期性的批量操作会导致 LRU 命中率急剧下降,缓存污染情况比较严重。
复杂度:
实现起来较为简单。
存储成本:
几乎没有空间上浪费。
代价:
命中时需要遍历链表,找到命中的数据块索引,然后需要将数据移到头部。
● 为什么要设计 后缀表达式,有什么好处?
考察点:逆波兰表达式
参考回答:
后缀表达式又叫逆波兰表达式,逆波兰记法不需要括号来标识操作符的优先级。
● 请你设计一个算法,用来压缩一段URL?
考察点:MD5加密算法
参考回答:
该算法主要使用MD5 算法对原始链接进行加密(这里使用的MD5 加密后的字符串长度为32 位),然后对加密后的字符串进行处理以得到短链接的地址。
● 谈一谈,id全局唯一且自增,如何实现?
考察点:SnowFlake雪花算法
参考回答;
SnowFlake雪花算法
雪花ID生成的是一个64位的二进制正整数,然后转换成10进制的数。64位二进制数由如下部分组成:
snowflake id生成规则
1位标识符:始终是0,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0。
41位时间戳:41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截 )得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间,由我们程序来指定的。
10位机器标识码:可以部署在1024个节点,如果机器分机房(IDC)部署,这10位可以由 5位机房ID + 5位机器ID 组成。
12位序列:毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号
优点
简单高效,生成速度快。
时间戳在高位,自增序列在低位,整个ID是趋势递增的,按照时间有序递增。
灵活度高,可以根据业务需求,调整bit位的划分,满足不同的需求。
缺点
依赖机器的时钟,如果服务器时钟回拨,会导致重复ID生成。
在分布式环境上,每个服务器的时钟不可能完全同步,有时会出现不是全局递增的情况。
● Design and implement a data structure for Least Frequently Used (LFU) cache. It should support the following operations: get and put. get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1. put(key, value) - Set or insert the value if the key is not already present. When the cache reaches its capacity, it should invalidate the least frequently used item before inserting a new item. For the purpose of this problem, when there is a tie (i.e., two or more keys that have the same frequency), the least recently used key would be evicted. Could you do both operations in O(1) time complexity?
考察点:LFU Cache
参考回答:
public class LFUCache {private class Node{ int value; ArrayListset; Node prev; Node next; public Node (int value ){ this.value = value; this.set = new ArrayList (); this.prev = null; this.next = null; }}private class item{ int key; int value; Node parent ; public item(int key ,int value, Node parent){ this.key = key ; this.value = value; this.parent = parent; }} private HashMap map;private Node head,tail;private int capacity;// @param capacity, an integerpublic LFUCache(int capacity) { // Write your code here this.capacity = capacity; this.map = new HashMap (); this.head = new Node (0); this.tail = new Node(Integer.MAX_VALUE); head.next = tail; tail.prev = head; }// @param key, an integer// @param value, an integer// @return nothingpublic void set(int key, int value) { // Write your code here if (get(key) != -1 ) { map.get(key).value = value; return ; } if (map.size() == capacity ){ getLFUitem(); } Node newpar = head.next; if ( newpar.value != 1){ newpar = getNewNode(1,head,newpar); } item curitem = new item(key,value,newpar); map.put(key,curitem); newpar.set.add(key); return; }public int get(int key) { // Write your code here if (!map.containsKey(key)){ return -1; } item cur = map.get(key); Node curpar = cur.parent; if (curpar.next.value == curpar.value + 1){ cur.parent= curpar.next; cur.parent.set.add(key); }else{ Node newpar =getNewNode(curpar.value + 1,curpar,curpar.next); cur.parent = newpar; newpar.set.add(key); } curpar.set.remove(new Integer(key)); if(curpar.set.isEmpty()){ deleteNode(curpar); } return cur.value;}private Node getNewNode (int value ,Node prev , Node next){ Node temp = new Node(value); temp.prev = prev; temp.next = next; prev.next = temp; next.prev = temp; return temp;}private void deleteNode(Node temp){ temp.prev.next = temp.next; temp.next.prev = temp.prev; return ;}private void getLFUitem(){ Node temp = head.next; int LFUkey = temp.set.get(0); temp.set.remove(0); map.remove(LFUkey); if (temp.set.isEmpty()){ deleteNode(temp); } return;}