Problem 1 LRU cache

https://leetcode.com/problems/lru-cache/

have been updated to new version

What is cache

  • 为了不每次都重新检索,所先把检索的结果存下来,下次用户访问的时候直接给存好的检索结果

  • 想到的是Map<key, value>, get a key by O(1)

  • look up operation(memo),如果计算过了,get上次计算的过,否则就重新算一遍

  • Cache 的本质就是map,只不过区别是,他是一个有capacity的map

What is used:

  • 所有的增改查insert, update query都是used

  • put operation == insert/ update

  • get operation == query

  • remove operation == delete

  • 注意,很多时候改,以为着是删/增

数据结构界的打工人:Map

  • Look up something(找什么,就是你所储存的东西) by something(透过什么来找,这个就是key)

  • 经典的Use Case: 我需要知道我自己定义的Entry Reference在哪里,但是我propose的结果不具备优秀的查找功能

    • Map<Key, Entry Reference> locationMap

    • key通过什么找reference,Entry Reference你其他的数据结构储存

  • Example: Array Based PriorityQueue Update(key) Function

    • Map<key, index> indexMapForKeyinHeap + Heap

  • 所有的操作,只要给我一个Key我都是可以拿到Entry Node, 又有了可以对时间排序的TreeSet/TreeMap

Summary

  • hybrid注意data structure, 先考虑每个元素cell里面有什么:

    • cell{key, value, index(hidden)}

  • 再注意,多加结构的时候,必须用cell来做一个单位,会让思路更简单,比如说这道题如果用treeSet

    • map(key, cell) && container(cell)

    • 并不能用map(key, index) && container(cell) , and Cell only have{key, index} ,why???

      • 因为如果你需要知道这个元素里的其他资料

/*
Clarification:
	- get
		- given key, to see value
		- return the value
	- put
		- given key, to edit value
		- update key access order(arbitrary)
		- update key's value ==> update value(look up)
		- if key is in?
			- remove from container& map
			- insert
			- append()
		- if the cache limit is not reach && key not in?
			- insert(K, V)
			- put the element into the access order ==> append()
		- if the cache limit is reach && key in?
			- delete() LRU element => deleteMin() (access order) & remove from map
			- insert(K, V)
			- append()
High Level:
	- we need a ds to maintain the access order --- find a sorted order sorted
	- Map + TreeSet 

Middle Level:
	- Unsorted? Map/ set
		- map? Key, value? O(1)
		- Pop? Check all then pop , O (n)
	- Half sorted? TreeMap, heap
		- container(element),Element sorted by index?
		- Map<key/element,value>?
	- Sorted ? List? Doublelinkedlist
		- Element? Key,value, element 
		- Linkedlist<element>
		- Map<element, index>
TC& SC

*/

Method 1 TreeSet/ TreeMap + Map

Step 1 Use Case API design

Step 2: Detail Logic

get(key):

  • case 1 : map 里面有这个key

    • 透过Map拿到Entry node(return的data就在里面)

    • update这个Entry Node的时间信息(TreeSet里的这个Entry做update--> delete + insert)

  • case 2: map里面没有这个key

    • return null

  • overall: O(logn)

put(key, value):

  • case 1: map 里面有这个key

    • 想当于update这个key-value pair里的value

    • 通过Map拿到Entry Node

      • update 这个Entry Node的value信息

      • update 这个Entry Node的时间信息

  • case 2: map 里面没有这个key

    • case 2.1 现在没有满

      • 直接放一个新的Entry(update two maps)

    • case 2.2 现在满了

      • 踢出一个leat recently used:也就是TreeSet里面时间戳

  • overall: O(logn)

timeStamp:

  • 设计为当前LRU class里的一个feild

  • updated 准则,任何operation都需要update当前的信息

capacity:

  • 设计为当前LRU class的一个field

  • 注意put的details操作

FYI

Timestamp today = new Timestamp(System.currentTimeMillis())

TreeSet Version

// Some code

// not correct

```java
```java
class LRUCache {
    private final int CAPACITY;
    private int timeStamp;
    private TreeSet<Entry> treeSet;
    private Map<Integer, Entry> map;
    public LRUCache(int capacity) {
        // assume cap valid
        this.CAPACITY = capacity;
        this.map = new HashMap<>();
        this.treeSet = new TreeSet<>();
        this.timeStamp = 0;
    }
    private void updateTimeStamp() {
        this.timeStamp++;
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        Entry cur = map.get(key);
        treeSet.remove(cur);
        updateTimeStamp();
        cur.timeStamp = this.timeStamp;
        treeSet.add(cur);
        System.out.println(cur.value);
        return cur.value;
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            Entry cur = map.get(key);
            treeSet.remove(cur);
            updateTimeStamp();
            cur.timeStamp = this.timeStamp;
            cur.value = value;
            treeSet.add(cur);
            return;
        }
        else {
            updateTimeStamp();
            Entry newEntry = new Entry(key, value, timeStamp);
            if (treeSet.size() == CAPACITY) {
                Entry leastRecentUsedEntry = treeSet.first();
                treeSet.remove(leastRecentUsedEntry);
                map.remove(leastRecentUsedEntry.key);
            }
            map.put(key, newEntry);
            treeSet.add(newEntry);
        }
    }
    class Entry implements Comparable<Entry> {
        int timeStamp;
        int key;
        int value;
        public Entry(int key, int value, int timeStamp) {
            this.timeStamp = timeStamp;
            this.key = key;
            this.value = value;
        }
        @Override
        public int compareTo(Entry other) {
            return Integer.compare(this.timeStamp, other.timeStamp);
        }
    }
}
// class LRUCache {
// 	class Cell implements Comparable<Cell>{
// 		int key;
// 		int val;
// 		int index;
// 		Cell(int key, int val, int index) {
// 			this.key = key;
// 			this.val= val;
// 			this.index = index;
// 		}
// 		@Override
// 		public int compareTo(Cell that) {
// 			int result = Integer.compare(this.index, that.index);
// 			int result_b = Integer.compare(this.key, that.key);
// 			if (result == 0) {
// 				return result_b;
// 			}
// 			return result;
// 		}
// 	}
// 	HashMap<Integer, Cell> map; // key ->  Cell
// 	TreeSet<Cell> minSet; // key, Cell
// 	int count;
// 	int capacity;
//     public LRUCache(int capacity) {
//         this.capacity = capacity;
// 		map = new HashMap<>();
// 		minSet = new TreeSet<>();
//     }
    
//     public int get(int key) {
//         Cell cur = map.get(key);
// 		if (cur == null) {
// 			return -1;
// 		}
// 		int value = cur.val;
// 		minSet.remove(cur);
// 		count++;
// 		Cell newCur = new Cell(key, value, count);
// 		minSet.add(newCur);
// 		map.put(key, newCur);
// 		return value;
//     }
    
//     public void put(int key, int value) {
//         Cell cur = map.get(key);
// 	count++;
// 	if (cur != null) {
// 		minSet.remove(cur);
// 		map.remove(cur);
// 	}
// 	else if (cur == null && map.size() == capacity) {
// 		Cell needRemove = minSet.pollFirst();
// 		map.remove(needRemove.key);
// 	}
// 	Cell curNew = new Cell(key, value, count);
// 	minSet.add(curNew);
// 	map.put(key, curNew);
//     }
// }

TreeMap Version

// Some code

class LRUCache {
    // treeMap version
    private final int CAPACITY;
    private int timeStamp;
    private Map<Integer, Integer> cache;
    private TreeMap<Integer, Entry> timeOrder; // TreeMap key is timeStamp
    public LRUCache(int capacity) {
        this.timeStamp = -1;
        this.cache = new HashMap<>();
        this.timeOrder = new TreeMap<>();
        this.CAPACITY = capacity;
    }
    
    public int get(int key) {
        if (cache.containsKey(key)) {
            Integer timeStamp = cache.get(key);
            Entry pair = timeOrder.get(timeStamp);
            increaseTimeStamp();
            updateTimeOrder(timeStamp, pair);
            return pair.getValue();
        }
        else {
            return -1;
        }
    }
    private void increaseTimeStamp() {
        this.timeStamp++;
    }
    private int getTimeStamp(){
        return this.timeStamp;
    }
    private int getCap() {
        return this.CAPACITY;
    }
    private void evictOldest(int time){
        Entry old = timeOrder.get(time);
        this.timeOrder.remove(time);
        this.cache.remove(old.getKey());
    }
    private void updateTimeOrder(int timeStamp, Entry pair) {
        evictOldest(timeStamp);
        timeOrder.put(getTimeStamp(), pair);
        cache.put(pair.getKey(), getTimeStamp());
    }
    private void addToCache(int currentTime, Entry pair) {
        timeOrder.put(currentTime, pair);
        cache.put(pair.getKey(), currentTime);
    }
    
    public void put(int key, int value) {
        if (cache.containsKey(key)) {
            Integer timeStamp = cache.get(key);
            Entry pair = timeOrder.get(timeStamp);
            pair.setValue(value);
            increaseTimeStamp();
            updateTimeOrder(timeStamp,pair);
        }
        else {
            Entry pair = new Entry(key, value);
            increaseTimeStamp();
            int time = getTimeStamp();
            if (isEmpty()) {
                addToCache(time, pair);
                return;
            }
            if (isFull()) {
                Integer oldestPairTimeStamp = timeOrder.firstKey();
                evictOldest(oldestPairTimeStamp);
            }
            addToCache(time, pair);
        }
    }
    public boolean isFull() {
        return getSize() == getCap();
    }
    public boolean isEmpty() {
        return getSize() == 0;
    }
    private int getSize() {
        return this.cache.size();
    }
    class Entry {
        private int key;
        private int value;
        public Entry(int key, int value) {
            this.key = key;
            this.value = value;
        }
        public int getKey() {
            return this.key;
        }
        public int getValue() {
            return this.value;
        }
        public void setKey(int key) {
            this.key = key;
        }
        public void setValue(int value) {
            this.value = value;
        }
    }
}
```

Method 2 DoubleLinkedList

  • 除了treeSet还有什么数据结构可以保持时间顺序? Queue==> FIFO时间顺序

  • Queue逻辑上的数据结构 => 怎么实现Queue

    • Queue<Integer> queue = new ArrayDeque<>();

    • Queue<Integer> queue = new LinkedList<>();

  • 增删改查全是O(1)

    • Queue是逻辑数据结构,底层实现的是LinkedList

    • 而DoubleLinkedList是因为O(1)的删除

  • 那你得给我这个node reference,有了这个node reference,我做什么都是O(1),关键问题是,人家User insert/update/remove/query,人家给你的是key,不是node reference

  • 打工人上线: Map<Key, DoublyListNode> locationMap + DoubleLinkedList(datainfo) DLL

Step 1 Use Case API design

  • get(key)

  • put(key, value)

Step 2: Detail Logic

get(key):

  • case 1 : map 里面有这个key

    • get the key==> 通过map拿到装有data的ListNode ==> ListNode里面的东西就是return的东西

    • update time order ==> remove this ListNode from LinkedList, then insert into Head

  • case 2: map里面没有这个key

    • return null

  • overall: O(logn)

put(key, value):

  • case 1: map 里面有这个key

    • update the value ==> 通过map拿到装有data的ListNode ==》 update ListNode

    • update the order ==> remove this ListNode from LinkedList, then insert into head

  • case 2: map 里面没有这个key

    • case 2.1 the cache is not full (check map 的size到不到capacity)

      • put the key value there

      • update the time order ==> insert into Head

    • case 2.2 现在满了

      • delete the oldest one ==> 踢出tail node

      • put the kye value there ==> insert into map and LL

      • update the time order ==> head

  • overall: O(1)

timeStamp:

  • 设计为当前LRU class里的一个feild

  • updated 准则,任何operation都需要update当前的信息

capacity

  • 设计为当前LRU class的一个field


import static java.lang.Math.nextDown;

/*
Element: <key, value, timeStamp>
<key, Element>
<Element_1> --> <Element_2>
get: given a key to see if we can find the value in the container(using the lookupMap check)
 in? find this element, rand update its timeStamp, put it back
 not? reutrn -1
put: given a key and value  
- container this key, find this element, update its value& timeStamp, and put it back
- not container, 
    - if reach the capacity, delete the least element
    - put this element into the container

*/


class LRUCache {
    // class Node implements Comparable<Node> {
    class Node {
        int key;
        int value;
        int timeStamp;
        Node prev;
        Node next;
        // public Node (int key, int value, int timeStamp) {
        public Node (int key, int value) {
            this.key = key;
            this.value = value;
            // this.timeStamp = timeStamp;
        }
        // @Override
        // public int compareTo(Node that) {
        //     return Integer.compare(this.timeStamp, that.timeStamp);
        // }
    }

    private Node head;
    private Node tail;
    private Map<Integer, Node> map;
    private final int CAPACITY;
    public LRUCache(int capacity) {
        this.CAPACITY = capacity;
        this.map = new HashMap<>();
    }
    
    public int get(int key) {
        if (!map.containsKey(key)) {
            return -1;
        }
        Node cur = map.get(key);
        deleteNode(cur);
        addNode(cur);
        return cur.value;
    }
    
    public void put(int key, int value) {
        if (map.containsKey(key)) {
            Node cur = map.get(key);
            cur.value = value;
            deleteNode(cur);
            addNode(cur);
        } else {
            if (map.size() == this.CAPACITY) {
                deleteNode(tail);
            }
            addNode(new Node(key, value));
        }
    }
    private void deleteNode(Node node)  {
        map.remove(node.key);
        if (node.prev != null && node.next != null) {
            Node prevNode = node.prev;
            Node nextNode = node.next;
            prevNode.next = nextNode;
            nextNode.prev = prevNode;
        }
        else if (node.prev == null && node.next != null) { // node = head, more than 2 node
            head = node.next;
            head.prev = null;
        }
        else if (node.prev != null && node.next == null) { // node = tail, more than 2 node
            tail = node.prev;
            tail.next = null;
        } else { // node = head = tail, only 1 element
            head = tail = null;
        }
        node.prev = node.next = null;
    }

    private void addNode(Node node)  {
        map.put(node.key, node);
        if (head == null) {
            head = tail = node;
        } else {
            node.next = head;
            head.prev = node;
            head = node;
        }
    }
}
```

Last updated