快慢指针

四个类似的问题:
1、 输入链表头部,奇数长度返回中点,偶数长度返回上中点;
2、 输入链表头部,奇数长度返回中点,偶数长度返回下中点;
3、 输入链表头部,奇数长度返回中点前一个,偶数长度返回上中点前一个;
4、 输入链表头部,奇数长度返回中点前一个,偶数长度返回下中点前一个;

处理逻辑都一样,都是设置一个快指针和一个慢指针,快指针每次遍历两个结点,慢指针每次遍历一个结点,当快指针无法往后遍历时,返回慢指针指向的节点。
唯一不同的就是慢指针和快指针的初始指向。

/**
 * 快慢指针
 */
public class LinkedList01 {
   
     

    /**
     * 输入链表头部,奇数长度返回中点,偶数长度返回上中点
     * @param head
     * @return
     */
    public static Node find01(Node head) {
   
     
        if (head == null || head.next == null) {
   
     
            return head;
        }

        Node slow = head;
        Node fast = head;
        while (fast.next != null && fast.next.next != null) {
   
     
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }

    /**
     * 输入链表头部,奇数长度返回中点,偶数长度返回下中点
     * @param head
     * @return
     */
    public static Node find02(Node head) {
   
     
        if (head == null || head.next == null) {
   
     
            return head;
        }

        Node slow = head.next;
        Node fast = head.next;
        while (fast.next != null && fast.next.next != null) {
   
     
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }

    /**
     * 输入链表头部,奇数长度返回中点前一个,偶数长度返回上中点前一个
     * @param head
     * @return
     */
    public static Node find03(Node head) {
   
     
        if (head == null || head.next == null) {
   
     
            return head;
        }

        Node slow = head;
        Node fast = head.next.next;
        while (fast.next != null && fast.next.next != null) {
   
     
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }

    /**
     * 输入链表头部,奇数长度返回中点前一个,偶数长度返回下中点前一个
     * @param head
     * @return
     */
    public static Node find04(Node head) {
   
     
        if (head == null || head.next == null) {
   
     
            return head;
        }

        Node slow = head;
        Node fast = head.next;
        while (fast.next != null && fast.next.next != null) {
   
     
            slow = slow.next;
            fast = fast.next.next;
        }

        return slow;
    }
    private static class Node {
   
     
        private Node next;
        private int value;
    }

}

输入一个链表头结点,判断链表是否是回文结构

三种方法:
1、 把链表结点压入栈中,此时栈中结点的顺序与链表相反,一个个弹出与链表结点比较即可;
2、 通过快慢指针,然慢指针指向链表中间节点,然后只把后半部分节点压入栈中,与列表节点进行比较,节省一半空间;
3、 通过快慢指针,然慢指针指向链表中间节点,然后把后把部分的节点的next指针反转,再用两个指针,一头一尾,遍历进行比较,比较完毕后还原后半部分节点的next指针;

第三种方法:
1、 先通过快慢(块指针每次走两步,慢指针每次走一步)指针,找到中点;
 

2、 把慢指针后面的点的指针逆序;
 

3、 左右指针遍历,左右点比对,指针某个指针遍历到null为止;
 

4、 遍历完毕后,还原链表;
 

/**
 * 输入一个链表头结点,判断链表是否是回文结构
 */
public class LinkedList02 {
   
     

    public static boolean isReverse01(Node head) {
   
     
        if (head == null && head.next == null)  return true;

        Stack<Node> stack = new Stack<>();
        Node help = head;
        //把链表中的所有结点压入栈中
        while (help != null) stack.push(help);
        boolean res = true;
        help = head;
        //此时从栈顶到栈底,结点顺序与链表相反,从链表头部开始遍历,每遍历一个节点,从栈中弹出一个进行比较
        while (!stack.isEmpty()) {
   
     
            res &= stack.pop().value == help.value;
            help = help.next;
        }
        return res;
    }

    public static boolean isReverse02(Node head) {
   
     
        if (head == null && head.next == null)  return true;

        //使用快慢指针,取到链表中点,只把链表后半部分压入栈中,与前半部分比较
        Node slow = head;
        Node fast = head;
        while (fast.next != null && fast.next.next != null) {
   
     
            slow = slow.next;
            fast = fast.next.next;
        }

        //把链表后半部分压入栈中
        Stack<Node> stack = new Stack<>();
        Node n2 = slow.next;
        boolean res = true;
        while (n2 != null) {
   
     
            stack.push(n2);
            n2 = n2.next;
        }

        //与前半部分比较
        n2 = head;
        while (!stack.isEmpty()) {
   
     
            res &= stack.pop().value == n2.value;
            n2 = n2.next;
        }

        return res;
    }

    public static boolean isReverse03(Node head) {
   
     
        if (head == null && head.next == null)  return true;

        //使用快慢指针,取到链表中点,把链表后半部分指针反转,然后与前半部分进行比较,笔记完毕后把指针还原
        Node slow = head;
        Node fast = head;
        while (fast.next != null && fast.next.next != null) {
   
     
            slow = slow.next;
            fast = fast.next.next;
        }

        //把链表后半部分指针反转
        Node n1 = slow;
        Node n2 = n1.next;
        n1.next = null;
        Node n3 = null;
        while (n2 != null) {
   
     
            n3 = n2.next;
            n2.next = n1;
            n1 = n2;
            n2 = n3;
        }

        //前后两半部分进行比较
        n3 = n1;
        n2 = head;
        boolean res = true;
        while (n1 != null && n2 != null) {
   
     
            res &= n1.value == n2.value;
            n1 = n1.next;
            n2 = n2.next;
        }

        //链表还原
        n1 = n3.next;
        n3.next = null;
        while (n1 != null) {
   
     
            n2 = n1.next;
            n1.next = n3;
            n3 = n1;
            n1 = n2;
        }
        return res;
    }

    private static class Node {
   
     
        private Node next;
        private int value;
    }

}

将单向链表按某值划分为左边小,中间等于,右边大于的形式

两种方法:
1、 把链表的每个节点放到数组里,做一次partition(笔试用);
2、 弄6个指针代表3个链表,分别是小于区域头指针、大于区域尾指针、等于区域头指针、等与区域尾指针、大于区域头指针、大于区域尾指针,遍历原链表,生成3个新链表,最后把这3个链表连起来,就是新的符合要求的链表(面试用);

第二种方法:
1、 遍历链表,往3个区域发货;
 
2、 遍历完后就是3个新链表;
 

3、 链好3个新链表,返回新的头结点;
 

/**
 * 将单向链表按某值划分为左边小,中间等于,右边大于的形式
 */
public class LinkedList03 {
   
     

    public static Node partition01(Node head, int pivot) {
   
     
        if (head == null || head.next == null) return head;

        //计算链表大小,并创建对应大小的数组,临时存放链表结点
        Node help = head;
        int size = 0;
        while (help != null) {
   
     
            size++;
            head = help.next;
        }
        Node[] arr = new Node[size];
        help = help;
        int index = 0;
        while (help != null) {
   
     
            arr[index++] = help;
            help = help.next;
        }

        //根据pivot基准值,对数组做partition操作
        int i1 = -1; //(...i1] 小于区
        int i2 = 0; // (i1...i2] 等于区
        int i3 = arr.length; // (i2...i3]大于区
        while (i2 != i3) {
   
     
            Node curr = arr[i2];
            if (curr.value < pivot) {
   
     
                swap(arr, ++i1, i2++);
            } else if (curr.value > pivot) {
   
     
                swap(arr, i2, --i3);
            } else {
   
     
                i2++;
            }
        }

        //遍历数组,把结点串起来
        Node prev = arr[0];
        for (int i = 1; i < arr.length; i++) {
   
     
            prev.next = arr[i];
            prev = arr[i];
        }

        return arr[0];
    }

    public static Node partition02(Node head, int pivot) {
   
     
        if (head == null || head.next == null) return head;

        Node smallHead = null; //小于区头结点
        Node smallTail = null; //小于区尾结点
        Node equalsHead = null; //等于区头结点
        Node equalsTail = null; //等于区尾结点
        Node bigHead = null; //大于区头结点
        Node bigTail = null; //大于区尾结点

        //遍历链表,组装小于区、等于区、大于区的链表,并且每遍历一个结点,就把原来的next指针断掉
        Node next;
        while (head != null) {
   
     
            next = head.next;
            head.next = null;
            if (head.value < pivot) {
   
     
                if (smallHead == null) {
   
     
                    smallHead = head;
                    smallTail = head;
                } else {
   
     
                    smallTail.next = head;
                    smallTail = head;
                }
            } else if (head.value == pivot) {
   
     
                if (equalsHead == null) {
   
     
                    equalsHead = head;
                    equalsTail = head;
                } else {
   
     
                    equalsTail.next = head;
                    equalsTail = head;
                }
            } else {
   
     
                if (bigHead == null) {
   
     
                    bigHead = head;
                    bigTail = head;
                } else {
   
     
                    bigTail.next = head;
                    bigTail = head;
                }
            }
            head = next;
        }

        //小于区的尾部连接等于区的头部,等于区的头部连接大于区的头部,但是要判断边界情况,就是小于区、等于区有可能为空
        if (smallHead != null) {
   
     
            smallTail.next = equalsHead;
            equalsTail = equalsHead == null ? smallTail : equalsTail;
        }
        if (equalsHead != null) {
   
     
            equalsTail = bigHead;
        }
        return smallHead != null ? smallHead : (equalsHead != null ? equalsHead : bigHead);
    }

    private static class Node {
   
     
        private int value;
        private Node next;
    }

    private static void swap(Node[] arr, int i, int j) {
   
     
        Node temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

}

链表结点带rand指针克隆问题

两种方法:
1、 遍历原链表,生成一个map,key是原链表节点,value是新的克隆结点,然后遍历map,处理每个克隆节点的next指针,和rand指针,两指针要连的节点,都可以通过原链表节点在map中找到(笔试用);
2、 遍历链表,生成克隆结点,生成克隆结点后,原链表节点的next指针指向克隆结点,然后克隆结点的next指针指向原链表节点的下一个节点,也就是克隆结点嵌在原链表中间;然后成对遍历链表,处理克隆结点的rand指针;然后在遍历一次链表,分离两个链表;

第二种方法:
1、 假设这是原链表;
 

2、 生成克隆结点,嵌在原链表中间;
 
3、 成对遍历链表,处理克隆结点的rand指针;
 
4、 分离出克隆结点链表;
 

/**
 * 链表结点带rand指针克隆问题
 */
public class LinkedList04 {
   
     

    public static Node clone01(Node head) {
   
     
        if (head == null) return null;

        //原结点 -> 克隆结点
        Map<Node, Node> map = new HashMap<>();

        //建立原结点与克隆结点的映射关系
        Node help = head;
        while (help != null) {
   
     
            Node copy = new Node();
            copy.value = help.value;
            map.put(help, copy);
            help = help.next;
        }

        //根据map中记录的原结点与克隆结点的映射关系,给克隆结点的next指针与rand指针赋值
        help = head;
        while (help != null) {
   
     
            Node copy = map.get(help);
            if (help.next != null) copy.next = map.get(help.next);
            if (help.rand != null) copy.rand = map.get(help.rand);
            help = help.next;
        }

        return map.get(head);
    }

    public static Node clone02(Node head) {
   
     

        //1、遍历列表,克隆结点,并把克隆结点连接到原结点后面
        //1->`1->`2->`2->`3->`3
        Node help = head;
        while (help != null) {
   
     
            Node next = help.next;
            Node copy = new Node();
            copy.value = help.value;
            help.next = copy;
            copy.next = next;
            help = next;
        }

        //2、遍历链表,处理克隆结点的rand指针
        help = head;
        while (help != null) {
   
     
            Node next = help.next.next;
            Node copy = help.next;
            if (help.rand != null) copy.rand = help.rand.next;
            help = next;
        }

        //3、链表分离
        Node res = help.next;
        help = head;
        while (help != null) {
   
     
            Node next = help.next.next;
            Node copy = help.next;
            help.next = next;
            if (next != null) copy.next = next.next;
            help = next;
        }

        return res;
    }

    private static class Node{
   
     
        private int value;
        private Node next;
        private Node rand;
    }

}

给定一个链表,如果有环则返回环中的第一个节点,没有则返回null

定一个快指针和一个慢指针,快指针一次走两步,慢指针一次走一步,两指针在环中相遇后,快指针回到头节点,两指针都每次走一步,它们必在入环节点相遇
 

/**
 * 给定一个链表,如果有环则返回环中的第一个节点,没有则返回null
 */
public class LinkedList05 {
   
     

    public static Node find(Node head) {
   
     
        if (head == null || head.next == null || head.next.next == null) return null;

        //快慢指针,如果有环,两指针会在环中相遇
        Node slow = head.next;
        Node fast = head.next.next;
        while (slow != fast) {
   
     
            if (fast == null || fast.next == null) return null;
            slow = slow.next;
            fast = fast.next.next;
        }

        //让fast指针回到头结点,每一次走一步,快慢指针相遇的结点,就是环中第一个节点
        fast = head;
        while (fast != null) {
   
     
            slow = slow.next;
            fast = fast.next;
        }

        return fast;

    }

    private static class Node {
   
     
        private int value;
        private Node next;
    }

}

给定两个无环链表,返回两个链表相交的第一个节点,没有则返回null

1、 先遍历两个链表,并记录长度;
2、 看两链表尾节点是否相等,是则代表有相交节点,否则两链表不相交;
3、 如果相交,长链表指针先走差值步,然后链表指针每次走一步,最后必在相交节点相遇;
 

/**
 * 给定两个无环链表,返回两个链表相交的第一个节点,没有则返回null
 */
public class LinkedList06 {
   
     

    public static Node findIntersectNode(Node head1, Node head2) {
   
     
        if (head1 == null || head2 == null) return null;

        //先遍历head1,计算head1长度
        int len = 0;
        Node curr1 = head1;
        while (curr1.next != null) {
   
     
            len++;
            curr1 = curr1.next;
        }

        //遍历head2,每遍历一个结点,len减一,计算出两个链表长度的差值
        Node curr2 = head2;
        while (curr2.next != null) {
   
     
            len--;
            curr2 = curr2.next;
        }

        //此时curr1和curr2都指向各自链表的尾结点,如果不是同一个尾结点,代表两链表没有相交,返回null
        if (curr1 != curr2) return null;

        //调整curr1和curr2指针,让cuur1指针指向长度较长的链表头结点
        curr1 = len < 0 ? head2 : head1;
        curr2 = curr1 == head1 ? head2 : head1;

        //此时len为两链表长度的差值,保证len为正数
        len = Math.abs(len);

        //curr1先走len步,使得curr1和curr2指向的结点开始到尾节点长度一样
        while (len != 0) {
   
     
            len--;
            curr1 = curr1.next;
        }

        //curr1和curr2每次各自走一步,最后必会到达相交处
        while (curr1 != curr2) {
   
     
            curr1 = curr1.next;
            curr2 = curr2.next;
        }

        return curr1;
    }

    private static class Node {
   
     
        private int value;
        private Node next;
    }

}

两个有环链表,给定链表头结点和环中第一个节点,找出两个链表相交的结点,没有相交则返回null

 

/**
 * 两个有环链表,给定链表头结点和环中第一个节点,找出两个链表相交的结点,没有相交则返回null
 */
public class LinkedList07 {
   
     

    public static Node findLoopIntersect(Node head1, Node loop1, Node head2, Node loop2) {
   
     
        Node curr1 = null;
        Node curr2 = null;
        // 第一种情况:两个链表在环外相交
        if (loop1 == loop2) {
   
     
            curr1 = head1;
            curr2 = head2;

            //遍历链表1,计算到入环结点的长度
            int len = 0;
            while (curr1 != loop1) {
   
     
                len++;
                curr1 = curr1.next;
            }

            //遍历链表二,也是到入环结点就停止,计算两个链表长度的差值
            while (curr2 != loop2) {
   
     
                len--;
                curr2 = curr2.next;
            }

            //调整curr1指向长度较长链表的头结点
            curr1 = len < 0 ? head2 : head1;
            curr2 = curr1 == head1 ? head2 : head1;

            //保证len为正数
            len = Math.abs(len);

            //curr1走len步,此时cuur1到入环结点与curr2到入环结点的长度相同
            while (len > 0) {
   
     
                len--;
                curr1 = curr1.next;
            }

            //curr1和curr2每次走1步,到入环结点或入环结点前必会相遇
            while (curr1 != curr2) {
   
     
                curr1 = curr1.next;
                curr2 = curr2.next;
            }

            return curr1;

        } else {
   
     
            //第二种情况,两个链表的入环结点不相同
            curr1 = loop1.next;
            //从loop1开始遍历链表1,看是否会与loop2相遇,是则两链表相交,返回loop1或loop2都可以,不会相遇则会回到loop1,返回null
            while (curr1 != loop1) {
   
     
                if (curr1 == loop2) return loop1;
                curr1 = curr1.next;
            }
            return null;
        }
    }

    private static class Node {
   
     
        private int value;
        private Node next;
    }

}

给定两个链表,有可能有环,也有可能无环,返回两个链表的相交结点,没有相交则返回null

可能的情况:

1、 两个链表都无环,退化为寻找两个无环链表相交结点的问题;

2、 一个链表有环,一个链表无环,不可能相交;

3、 两个链表都有环,分三种情况:;

3、 1、两个有环链表,不相交;

3、 2、两个有环链表,在环外相交(包括入环节点);

3、 3、两个有环链表,在环内相交(相交但是入环节点不同);

解法:

1、 先分别通过链表寻找入环节点的方法,找到两个链表的入环节点;
2、 如果两个入环节点都是null,则符号情况1,用寻找两个无环链表相交结点的方法求解;
3、 如果一个入环节点为null,另一个不为null,则符号情况2,返回null;
4、 如果两个入环节点都不为null,如果两个入环节点相等,则是情况3.2,以入环节点为终点,利用两无环链表寻找相交点的方式求交点;
5、 如果两个入环节点都不为null,以其中一个入环节点为起点,另一个入环节点为终点,从起点出发转一圈,看是否能到达终点,如果回到起点都没遇到终点,则是情况3.1,返回null;如果遇到终点,返回两个入环节点的其中一个就行;

/**
 * 给定两个链表,有可能有环,也有可能无环,返回两个链表的相交结点,没有相交则返回null
 */
public class LinkedList08 {
   
     

    public static Node find(Node head1, Node head2) {
   
     
        if (head1 == null || head2 == null) return null;

        //1、找出两个链表的入环结点
        Node loop1 = findLoopNode(head1);
        Node loop2 = findLoopNode(head2);

        //如果loop1和loop2都为null,则退化为寻找两个无环链表相交结点的问题
        if (loop1 == null && loop2 == null) {
   
     
            return findIntersectNodeNoLoop(head1, head2);
        }

        //如果loop1和loop2都不为空,则简化为寻找两个有环链表的相交结点问题
        if (loop1 != null && loop2 != null) {
   
     
            return findLoopIntersect(head1, loop1, head2, loop2);
        }

        //如果一个链表有环,一个链表无环,不可能相交,返回null
        return null;
    }

    /**
     * 找出两个有环链表的相交结点,没有则返回null
     * @param head1
     * @param loop1
     * @param head2
     * @param loop2
     * @return
     */
    public static Node findLoopIntersect(Node head1, Node loop1, Node head2, Node loop2) {
   
     
        Node curr1 = null;
        Node curr2 = null;
        // 第一种情况:两个链表在环外相交
        if (loop1 == loop2) {
   
     
            curr1 = head1;
            curr2 = head2;

            //遍历链表1,计算到入环结点的长度
            int len = 0;
            while (curr1 != loop1) {
   
     
                len++;
                curr1 = curr1.next;
            }

            //遍历链表二,也是到入环结点就停止,计算两个链表长度的差值
            while (curr2 != loop2) {
   
     
                len--;
                curr2 = curr2.next;
            }

            //调整curr1指向长度较长链表的头结点
            curr1 = len < 0 ? head2 : head1;
            curr2 = curr1 == head1 ? head2 : head1;

            //保证len为正数
            len = Math.abs(len);

            //curr1走len步,此时cuur1到入环结点与curr2到入环结点的长度相同
            while (len > 0) {
   
     
                len--;
                curr1 = curr1.next;
            }

            //curr1和curr2每次走1步,到入环结点或入环结点前必会相遇
            while (curr1 != curr2) {
   
     
                curr1 = curr1.next;
                curr2 = curr2.next;
            }

            return curr1;

        } else {
   
     
            //第二种情况,两个链表的入环结点不相同
            curr1 = loop1.next;
            //从loop1开始遍历链表1,看是否会与loop2相遇,是则两链表相交,返回loop1或loop2都可以,不会相遇则会回到loop1,返回null
            while (curr1 != loop1) {
   
     
                if (curr1 == loop2) return loop1;
                curr1 = curr1.next;
            }
            //第三种情况,两链表不相交
            return null;
        }
    }

    /**
     * 找出两个无环链表的相交几点,没有则返回null
     * @param head1
     * @param head2
     * @return
     */
    public static Node findIntersectNodeNoLoop(Node head1, Node head2) {
   
     
        if (head1 == null || head2 == null) return null;

        //先遍历head1,计算head1长度
        int len = 0;
        Node curr1 = head1;
        while (curr1.next != null) {
   
     
            len++;
            curr1 = curr1.next;
        }

        //遍历head2,每遍历一个结点,len减一,计算出两个链表长度的差值
        Node curr2 = head2;
        while (curr2.next != null) {
   
     
            len--;
            curr2 = curr2.next;
        }

        //此时curr1和curr2都指向各自链表的尾结点,如果不是同一个尾结点,代表两链表没有相交,返回null
        if (curr1 != curr2) return null;

        //调整curr1和curr2指针,让cuur1指针指向长度较长的链表头结点
        curr1 = len < 0 ? head2 : head1;
        curr2 = curr1 == head1 ? head2 : head1;

        //此时len为两链表长度的差值,保证len为正数
        len = Math.abs(len);

        //curr1先走len步,使得curr1和curr2指向的结点开始到尾节点长度一样
        while (len != 0) {
   
     
            len--;
            curr1 = curr1.next;
        }

        //curr1和curr2每次各自走一步,最后必会到达相交处
        while (curr1 != curr2) {
   
     
            curr1 = curr1.next;
            curr2 = curr2.next;
        }

        return curr1;
    }

    /**
     * 找出链表入环结点,无环则返回null
     * @param head
     * @return
     */
    public static Node findLoopNode(Node head) {
   
     
        if (head == null || head.next == null || head.next.next == null) return null;

        //快慢指针,如果有环,两指针会在环中相遇
        Node slow = head.next;
        Node fast = head.next.next;
        while (slow != fast) {
   
     
            if (fast == null || fast.next == null) return null;
            slow = slow.next;
            fast = fast.next.next;
        }

        //让fast指针回到头结点,每一次走一步,快慢指针相遇的结点,就是环中第一个节点
        fast = head;
        while (fast != null) {
   
     
            slow = slow.next;
            fast = fast.next;
        }

        return fast;

    }
    
    private static class Node {
   
     
        private int value;
        private Node next;
    }
    
}

补充-LUR算法

简单版-直接使用系统提供的api

class LRUCache extends LinkedHashMap {
   
     
	private int capacity;
	
	public LRUCache(int capacity) {
   
     
	    super(capacity, 0.75F, true);
	    this.capacity = capacity;
	}
	
	public int get(int key) {
   
     
	    return (int)super.getOrDefault(key, -1);
	}
	
	public void put(int key, int value) {
   
     
	    super.put(key, value);
	}
	protected boolean removeEldestEntry(Map.Entry eldest) {
   
     
	    return size() > capacity;
	}
}

纯手写版:

/**
 * LRU内存替换算法
 * Created by huangjunyi on 2022/10/1.
 */
public class _01LRU {
   
     

    private static class Node<K, V> {
   
     
        private Node last;
        private Node next;
        private K key;
        private V value;

        public Node(K key, V value) {
   
     
            this.key = key;
            this.value = value;
        }
    }

    private static class DoubleLinkedList<K, V> {
   
     

        private Node<K, V> head;
        private Node<K, V> tail;

        public DoubleLinkedList() {
   
     
            this.head = null;
            this.tail = null;
        }

        public void add (Node<K, V> node) {
   
     
            if (node == null) return;
            if (head == null) head = tail = node;
            else {
   
     
                node.last = tail;
                tail.next = node;
                tail = node;
            }
        }

        public void moveToTail(Node<K, V> node) {
   
     
            if (node == tail) return;
            else if (node == head) {
   
     
                head = node.next;
                head.last = null;
            } else {
   
     
                node.last.next = node.next;
                node.next.last = node.last;
            }
            node.last = tail;
            node.next = null;
            tail.next = node;
            tail = node;
        }

        public Node<K, V> removeCurrHead() {
   
     
            if (head == null) return null;
            Node res = head;
            if (head == tail) {
   
     
                head = null;
                tail = null;
            } else {
   
     
                head = res.next;
                res.next = null;
                head.last = null;
            }
            return res;
        }

    }

    private static class LRUCache<K, V> {
   
     

        private HashMap<K, Node<K, V>> keyNodeMap;
        private DoubleLinkedList<K, V> list;
        private int capacity;

        public LRUCache(int capacity) {
   
     
            keyNodeMap = new HashMap<>();
            list = new DoubleLinkedList<>();
            this.capacity = capacity;
        }

        public void put(K key, V value) {
   
     
            if (keyNodeMap.containsKey(key)) {
   
     
                Node<K, V> node = keyNodeMap.get(key);
                node.value = value;
                list.moveToTail(node);
            } else {
   
     
                if (keyNodeMap.size() == capacity) {
   
     
                    Node<K, V> node = list.removeCurrHead();
                    if (node != null) keyNodeMap.remove(node.key);
                    Node<K, V> newNode = new Node<>(key, value);
                    keyNodeMap.put(key, newNode);
                    list.add(newNode);
                }
            }
        }

    }
}