public void set(int key, int value) {
            // case 1: the map contains key: renew value, move the key node to the head
            if (map.containsKey(key)) {
                /*Node keyNode = head;
                while (keyNode.data != key) {
                    keyNode = keyNode.next;
                }*/
                Node keyNode = keyToNodeMap.get(key);
                if (tail == head) { // there is exactly one element
                    head.data = key;
                } else { // more than 1 element
                    if (keyNode.prev == null) { // this is the head
                        keyNode.data = key;
                    } else { // the key element is somewhere in the middle
                        keyNode.prev.next = keyNode.next;
                        if (keyNode.next != null) {
                            keyNode.next.prev = keyNode.prev;
                        }
                        if (tail == keyNode) {
                            tail = keyNode.prev;
                        }

                        head.prev = keyNode;
                        keyNode.next = head;
                        head = keyNode;
                        head.prev = null;
                        keyToNodeMap.put(key, head);
                    }
                }
                map.put(key, value);
            } else { // this is a brand new element
                if (size == capacity) { // reached capacity
                    map.remove(tail.data);
                    keyToNodeMap.remove(tail.data);
                    tail = tail.prev;
                    if (tail != null) tail.next = null;
                    size--;
                }
                Node newNode = new Node(key, null, head);
                if (head != null) head.prev = newNode;
                if (tail == null) {
                    tail = newNode;
                }
                head = newNode;
                size++;
                map.put(key, value);
                keyToNodeMap.put(key, head);
            }
        }