LeetCode 热题 100 | 链表

**摘要:**本文聚焦LeetCode热题100链表板块,解析高频考点与经典解法。

链表基础

单向链表

java 复制代码
class ListNode {
    int val;
    ListNode next;

    ListNode(int x) {
        val = x;
        next = null;
    }
}

public class SinglyLinkedList implements Iterable<Integer> {
    private ListNode head; 

    // ==================== 1. 头部添加 ====================
    public void addFirst(int value) {
        ListNode newNode = new ListNode(value); 
        newNode.next = this.head; 
        this.head = newNode;
    }

    // ==================== 2. 遍历相关 ====================
    public void loopWhile(Consumer<Integer> consumer) {
        ListNode curr = this.head;
        while (curr != null) {
            consumer.accept(curr.val); 
            curr = curr.next;
        }
    }

    public void loopFor(Consumer<Integer> consumer) {
        for (ListNode curr = this.head; curr != null; curr = curr.next) {
            consumer.accept(curr.val); 
        }
    }


    private class NodeIterator implements Iterator<Integer> {
        ListNode curr = head; 

        @Override
        public boolean hasNext() {
            return curr != null;
        }

        @Override
        public Integer next() {
            int value = curr.val;
            curr = curr.next;
            return value;
        }
    }

    @Override
    public Iterator<Integer> iterator() {
        return new NodeIterator();
    }

    public void loopRecursion() {
        recursion(this.head);
    }

    private void recursion(ListNode curr) {
        if (curr == null) {
            return;
        }
        System.out.print(curr.val + " ");
        recursion(curr.next);
    }

    // ==================== 3. 尾部添加 ====================
    private ListNode findLast() {
        if (this.head == null) {
            return null;
        }
        ListNode curr;
        for (curr = this.head; curr.next != null; ) {
            curr = curr.next;
        }
        return curr;
    }


    public void addLast(int value) {
        ListNode last = findLast();
        if (last == null) {
            addFirst(value);
            return;
        }
        ListNode newNode = new ListNode(value); 
        last.next = newNode;
    }


    public void addLast(int first, int... rest) {
        ListNode sublist = new ListNode(first);
        ListNode curr = sublist;
        for (int value : rest) {
            ListNode newNode = new ListNode(value);
            curr.next = newNode;
            curr = curr.next;
        }

        ListNode last = findLast();
        if (last == null) {
            this.head = sublist;
            return;
        }
        last.next = sublist;
    }

    // ==================== 4. 根据索引操作 ====================
    private ListNode findNode(int index) {
        int i = 0;
        for (ListNode curr = this.head; curr != null; curr = curr.next, i++) {
            if (index == i) {
                return curr;
            }
        }
        return null;
    }

    
    private IllegalArgumentException illegalIndex(int index) {
        return new IllegalArgumentException(String.format("index [%d] 不合法", index));
    }


    public int get(int index) {
        ListNode node = findNode(index);
        if (node != null) {
            return node.val;
        }
        throw illegalIndex(index);
    }

    // ==================== 5. 插入节点 ====================
    public void insert(int index, int value) {
        if (index == 0) {
            addFirst(value);
            return;
        }
        
        ListNode prev = findNode(index - 1);
        if (prev == null) { 
            throw illegalIndex(index);
        }
        
        ListNode newNode = new ListNode(value); 
        newNode.next = prev.next; 
        prev.next = newNode; 
    }

    // ==================== 6. 删除节点 ====================
    public void remove(int index) {
        if (index == 0) {
            if (this.head != null) {
                this.head = this.head.next;
                return;
            } else {
                throw illegalIndex(index);
            }
        }

        ListNode prev = findNode(index - 1);
        ListNode curr;
        if (prev != null && (curr = prev.next) != null) {
            prev.next = curr.next;
        } else {
            throw illegalIndex(index);
        }
    }
}

**虚拟头结点:**链表内还有一种特殊的节点称为哨兵(Sentinel)节点,也叫做哑元( Dummy)节点,它不存储数据,通常用作头尾,用来简化边界判断。

单向链表(带哨兵)

java 复制代码
class ListNode {
    int val;
    ListNode next;
    ListNode(int x) {
        val = x;
        next = null;
    }
}


public class SinglyLinkedListSentinel implements Iterable<Integer> {
    private ListNode head = new ListNode(0); 

    private ListNode findNode(int index) {
    
        int i = -1;
        for (ListNode curr = this.head; curr != null; curr = curr.next, i++) {
            if (i == index) {
                return curr;
            }
        }
        return null;
    }

    //查找最后一个节点
    private ListNode findLast() {
        ListNode curr;

        for (curr = this.head; curr.next != null; ) {
            curr = curr.next;
        }
        return curr;
    }

    private IllegalArgumentException illegalIndex(int index) {
        return new IllegalArgumentException(String.format("索引 [%d] 不合法", index));
    }


     //头部添加节点
    public void addFirst(int value) {
        ListNode newNode = new ListNode(value); 
        newNode.next = this.head.next; 
        this.head.next = newNode; 
    }

     //尾部添加节点
    public void addLast(int value) {
        ListNode last = findLast();
        ListNode newNode = new ListNode(value); 
        last.next = newNode;
    }

    //尾部添加多个节点
    public void addLast(int first, int... rest) {
        ListNode sublist = new ListNode(first);
        ListNode curr = sublist;
        
        for (int value : rest) {
            ListNode newNode = new ListNode(value);
            curr.next = newNode;
            curr = curr.next;
        }
        
        ListNode last = findLast();
        last.next = sublist;
    }

    //指定索引插入节点
    public void insert(int index, int value) {
        ListNode prev = findNode(index - 1); // index=0 时,prev 是哨兵节点
        
        if (prev != null) {
            ListNode newNode = new ListNode(value); 
            newNode.next = prev.next; 
            prev.next = newNode; 
        
        } else {
            throw illegalIndex(index);
        }
    }

    //指定索引删除节点
    public void remove(int index) {
        ListNode prev = findNode(index - 1); 
        ListNode curr;
        
        if (prev != null && (curr = prev.next) != null) {
            prev.next = curr.next; 
        } else {
            throw illegalIndex(index);
        }
    }

    //根据索引获取节点值
    public int get(int index) {
        ListNode node = findNode(index);
        if (node != null) {
            return node.val;
        }
        throw illegalIndex(index);
    }

    //while 遍历
    public void loopWhile(Consumer<Integer> consumer) {
        ListNode curr = this.head.next;
        while (curr != null) {
            consumer.accept(curr.val);
            curr = curr.next;
        }
    }

    //for 遍历
    public void loopFor(Consumer<Integer> consumer) {
        for (ListNode curr = this.head.next; curr != null; curr = curr.next) {
            consumer.accept(curr.val);
        }
    }

    //迭代器遍历
    private class NodeIterator implements Iterator<Integer> {
        ListNode curr = head.next;

        @Override
        public boolean hasNext() {
            return curr != null;
        }

        @Override
        public Integer next() {
            int value = curr.val;
            curr = curr.next;
            return value;
        }
    }

    @Override
    public Iterator<Integer> iterator() {
        return new NodeIterator();
    }

    //递归遍历
    public void loopRecursion() {
        recursion(this.head.next);
    }

    private void recursion(ListNode curr) {
        if (curr == null) {
            return;
        }
        System.out.print(curr.val + " ");
        recursion(curr.next);
    }
}

160 相交链表

**核心逻辑:**通过 "双指针边界对齐法" 让两个指针遍历两个链表 ------ 利用「指针遍历完自身链表后切换到另一链表头部」的特性,每次遍历可消除两个链表的长度差,逐步对齐指针的遍历边界,无需额外空间且时间复杂度最优,高效找到相交节点。


关键步骤:

边界初始化:定义两个指针分别指向两个链表头部(nodeA=headA,nodeB=headB);

边界对齐遍历:循环判断 nodeA != nodeB(未相遇),通过切换指针遍历路径对齐边界:

  1. 若 nodeA 遍历到自身链表末尾(nodeA == null):切换到另一链表头(nodeA=headB),消除长度差;

  2. 若 nodeA 未到末尾:继续遍历自身链表(nodeA=nodeA.next);

  3. 若 nodeB 遍历到自身链表末尾(nodeB == null):切换到另一链表头(nodeB=headA),消除长度差;

  4. 若 nodeB 未到末尾:继续遍历自身链表(nodeB=nodeB.next);

终止返回:当循环因 nodeA == nodeB 终止时,返回该节点(要么是相交,要么是 null 无交点)。

java 复制代码
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null) {
            return null;
        }
        
        ListNode nodeA = headA; 
        ListNode nodeB = headB; 
        
        while (nodeA != nodeB) {
            nodeA = (nodeA == null) ? headB : nodeA.next;
            nodeB = (nodeB == null) ? headA : nodeB.next;
        }
        
        return nodeA;
    }
}

206 反转链表

**核心逻辑:**通过 "迭代式指针反转法" 从链表头节点起始遍历 ------ 利用「链表节点仅能单向指向后继节点」的特性,以头节点为起始点,借助三个指针(前驱 / 当前 / 后继)每次反转一个节点的指向,逐步推进遍历边界,无需额外空间且时间复杂度最优,高效完成链表反转。


关键步骤:

边界初始化:定义前驱指针 prev = null、当前指针 curr = head;

迭代反转遍历:循环判断 curr != null

  1. 临时存储后继:定义 temp = curr.next(保存当前节点的后继节点,避免反转后丢失链表后续节点);

  2. 反转节点指向:curr.next = prev(将当前节点的指针从指向后继改为指向前驱,完成单次反转);

  3. 推进遍历边界:prev = curr (前驱指针后移,指向当前已完成反转的节点);curr = temp(当前指针后移,指向待处理的后继节点);

终止返回:当循环因 curr == null终止时,prev 已指向原链表的最后一个节点,返回 prev。

java 复制代码
public ListNode reverseList(ListNode head) {
        if (head == null) {
            return null;
        }

        ListNode prev = null;
        ListNode curr = head;

        while (curr != null) {
            ListNode temp=curr.next;
            curr.next=prev;
            prev=curr;
            curr=temp;
        }
return prev;
    }

234 回文链表

核心逻辑: 利用回文的中心对称特性,先将链表节点存入数组实现随机访问,再通过双指针从数组首尾向中间收缩验证,判断是否为回文。


关键步骤:

  1. 边界初始化:空链表直接返回 false;定义数组和遍历指针指向链表头。

  2. 链表转数组:遍历链表,将所有节点存入数组。

  3. 双指针验证:左指针从数组头、右指针从数组尾开始,对比对应位置值,指针向中间收缩,直至相遇。

  4. 终止返回:验证完成则返回 true。

java 复制代码
public boolean isPalindrome(ListNode head) {
        List<ListNode> list = new ArrayList<>();
        
        if (head == null) {
            return false;
        }
        
        ListNode curr=head;
        
        while (curr!=null){
            list.add(curr);
            curr=curr.next;
        }
        
        int left=0;
        int right=list.size()-1;

        while (left<=right){
            if(list.get(left).val!=list.get(right).val){
                return false;
            }
            right--;
            left++;
        }
        return true;
    }

141 环形链表

**核心逻辑:**通过 "快慢指针追击法" 遍历链表 ------ 利用「环形链表中快指针终将追上慢指针,无环链表中快指针会先到末尾」的特性,以快慢双指针从链表头出发,快指针步速 2、慢指针步速 1,逐步遍历验证,无需额外空间且时间复杂度最优,高效判断链表是否有环。


关键步骤:

  1. 边界初始化:定义快指针 fast = head、慢指针 slow = head(均从链表头起始);

  2. 快慢指针遍历:循环判断 fast != null 且 fast.next != null(快指针未到链表末尾):

    1. 快指针步进:fast = fast.next.next(步速 2,快速遍历);

    2. 慢指针步进:slow = slow.next(步速 1,慢速遍历);

    3. 追击验证:若 fast == slow(快慢指针相遇),说明链表有环,直接返回 true;

  3. 终止返回:当循环因快指针越界终止时,说明链表无环,返回 false。

java 复制代码
public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }

        ListNode fast=head;
        ListNode slow=head;

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

            if(fast==slow){
                return true;
            }
        }
        
        return false;
    }

142 环形 链表 II

**核心逻辑:**利用快慢指针相遇后,将慢指针重置到链表头,两指针同速遍历终将在环入口相遇的数学特性,分两步操作:先找快慢指针相遇点,再通过同速遍历推导环入口,无需额外空间且时间复杂度最优。


关键步骤:

边界初始化:定义快指针 fast = head、慢指针 slow = head(均从链表头起始);

找快慢指针相遇点,循环判断 fast != null 且 fast.next != null(快指针未到链表末尾):

  • 慢指针步进:slow = slow.next(步速 1);

  • 快指针步进:fast = fast.next.next(步速 2);

  • 相遇验证:若 slow == fast(快慢指针相遇),跳出循环进入阶段 2;

推导环的入口节点:

  1. 重置慢指针:slow = head(放回链表起始位置);

  2. 同速遍历验证:循环判断 slow != fast(两指针未相遇),快慢指针均以步速 1 遍历;

终止返回:当 slow == fast 时,该节点即为环的入口节点,返回 slow。

java 复制代码
public ListNode detectCycle(ListNode head) {
        if (head == null || head.next == null) {
            return null;
        }

        // 阶段1:找到相遇点
        ListNode slow = head; 
        ListNode fast = head; 

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

            if (slow == fast) {
                break;
            }
        }

        // 阶段2:找环的入口
        // 关键操作:把慢指针放回起点,快慢指针都改为一次走1步
        slow = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
}

21 合并有序链表

**核心逻辑:**通过 "虚拟头节点 + 双指针归并法" 合并链表 ------ 利用「两个链表本身有序」的特性,以虚拟头节点简化边界处理,双指针分别遍历两个链表,每次选取较小值节点接入结果链表,逐步归并,无需额外空间且时间复杂度最优,高效完成有序链表合并。


关键步骤:

  1. 边界初始化:定义虚拟头节点 dummy,定义归并指针 current = dummy(指向结果链表的当前末尾);

  2. 双指针归并遍历:循环判断 list1 != null 且 list2 != null(两个链表均未遍历完):

    1. 取值对比:若 list1.val <= list2.val,将 list1 接入结果链表(current.next = list1),list1 指针后移;

    2. 反之,将 list2 接入结果链表(current.next = list2),list2 指针后移;

    3. 推进结果链表:current = current.next

  3. 处理剩余节点:循环结束后,将未遍历完的链表剩余部分直接接入结果链表(current.next = list1 或 list2);

  4. 终止返回:返回虚拟头节点的后继节点(dummy.next),即合并后链表的真实头节点。

java 复制代码
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy = new ListNode(-1); 
        ListNode current = dummy;
        
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                current.next = list1;
                list1 = list1.next;
            } else {
                current.next = list2;
                list2 = list2.next;
            }
            current = current.next;
        }
        
        current.next = (list1 != null) ? list1 : list2;
        
        return dummy.next;
    }

2 两数相加

**核心逻辑:**通过 "虚拟头节点 + 逐位累加进位法" 模拟手工加法 ------ 利用「链表逆序存储数字(头为个位)」的特性,以虚拟头节点简化结果链表边界处理,逐位累加两个链表节点值并带上一轮进位,计算当前位值和新进位,逐步构建结果链表,无需额外空间且时间复杂度最优,高效完成链表版两数相加。


关键步骤:

边界初始化:定义虚拟头节点 dummy,结果链表游标指针 curr = dummy,进位变量 carry = 0;

逐位累加遍历:循环判断 l1 != null 或 l2 != null 或 carry != 0(任一链表未遍历完 / 仍有进位):

  1. 初始化累加和:sum = carry(先带上一轮进位);

  2. 累加 l1 当前位:若 l1 非空,sum += l1.val,l1 指针后移;

  3. 累加 l2 当前位:若 l2 非空,sum += l2.val,l2 指针后移;

  4. 计算进位和当前位:carry = sum / 10(取整得进位),value = sum % 10(取余得位值);

  5. 构建结果链表:curr.next = new ListNode (value)(当前位接入结果链表),curr 指针后移;

终止返回:循环结束后,返回 dummy.next(虚拟头节点的后继节点,即结果链表真实头节点)。

java 复制代码
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy=new ListNode(0);
        ListNode curr=dummy;
        int carry=0;
        int sum=0;

        while (l1!=null || l2!=null || carry!=0){
            sum=carry;

            if(l1!=null){
                sum+=l1.val;
                l1=l1.next;
            }

            if(l2!=null){
                sum+=l2.val;
                l2=l2.next;
            }

            carry=sum/10;
            sum=sum%10;
            curr.next=new ListNode(sum);
            curr=curr.next;
        }
        
    return dummy.next;
    }

19 删除链表的倒数第 N 个结点

核心逻辑:通过 "快慢指针间隔定位法" 定位待删节点 ------ 利用「快慢指针拉开 n+1 步间隔后同速遍历,快指针到末尾时慢指针恰好指向待删节点前驱」的特性,以虚拟头节点规避边界问题,无需计算链表长度且时间复杂度最优,高效删除倒数第 N 个节点。


关键步骤:

  1. 边界初始化:定义虚拟头节点 dummy,dummy.next = head;定义慢指针 slow = dummy、快指针 fast = dummy**(均从虚拟头节点起始)**;

  2. 快慢指针拉开间隔:循环 n+1 次,让 fast 指针先走 n+1 步(确保慢指针最终指向待删节点的前驱):

    1. 每次循环执行 fast = fast.next;
  3. 同速遍历定位前驱:循环判断 fast != null(快指针未到链表末尾):

    1. 慢指针步进:slow = slow.next(步速 1);

    2. 快指针步进:fast = fast.next(步速 1);

    3. 当 fast 为 null 时,slow 恰好指向「倒数第 n+1 个节点」(待删节点的直接前驱);

  4. 删除节点并返回:执行 slow.next = slow.next.next;返回 dummy.next。

java 复制代码
public ListNode removeNthFromEnd(ListNode head, int n) {
      ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode slow = dummy;
        ListNode fast = dummy;

        for (int i = 0; i <= n; i++) {
            fast = fast.next;
        }

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

        slow.next = slow.next.next;

        return dummy.next;
    }

24 两两交换链表中的节点

**核心逻辑:**通过 "虚拟头节点 + 迭代交换法" 实现节点两两交换 ------ 利用「虚拟头节点简化头节点交换的边界处理」的特性,以虚拟头节点为起始点,每次迭代锁定待交换的两个节点,通过调整指针指向完成局部交换,逐步推进遍历边界,无需额外空间且时间复杂度最优,高效完成两两交换。


关键步骤:

边界初始化:定义虚拟头节点 dummy,dummy.next = head;定义遍历指针 curr = dummy;

迭代交换遍历:循环判断 curr.next != null 且 curr.next.next != null(存在可交换的两个节点):

  1. 锁定待交换节点:定义 node1 = curr.next(第一个节点)、node2 = curr.next.next(第二个节点);

  2. 调整指针完成交换:

    • curr.next = node2(前驱节点指向第二个待交换节点,完成交换第一步);

    • node1.next = node2.next(第一个节点指向第二个节点的后继,避免链表断裂);

    • node2.next = node1(第二个节点指向第一个节点,完成交换第二步);

准备下一轮交换:curr = curr.next.next;

终止返回:当循环因无足够可交换节点终止时,返回 dummy.next。

java 复制代码
public ListNode swapPairs(ListNode head) {
    ListNode dummy=new ListNode(0,head);
    ListNode curr=dummy;

    while (curr.next!=null && curr.next.next!=null){
        ListNode node1=curr.next;
        ListNode node2=curr.next.next;
        curr.next=node2;
        node1.next=node2.next;
        node2.next=node1;
        curr=curr.next.next;
    }
    return dummy.next;
}

138 随机链表的复制

核心逻辑:通过 "三步复制法" 实现深拷贝 ------ 利用链表的链式遍历特性,先在原链表每节点后新增复制节点,再依次设定复制节点的 random 指针,最后拆分原链表与复制链表,通过三次线性遍历完成无哈希表依赖的深拷贝,确保所有指针指向全新节点。


关键步骤

  1. 节点插入复制 :遍历原链表,在每个原节点后新建一个复制节点,将原节点值写入新节点,使原链表变为 原节点 -> 复制节点 -> 原节点.next -> ... 的交错结构。

  2. 指定 random 指针:再次遍历链表,定位到复制节点,将其 random 指针指向对应原节点的 random 指针所指向节点的下一个节点(即复制节点),完成 random 指针的深指向。

  3. 拆分 链表 分离:第三次遍历链表,拆出交错链表中的复制节点,重新构建成新的复制链表,同时恢复原链表的 next 指针,返回复制链表头节点。

java 复制代码
    public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }

        Node cur = head;
        while (cur != null) {
            Node copyNode = new Node(cur.val); 
            copyNode.next = cur.next;          
            cur.next = copyNode;               
            cur = copyNode.next;               
        }

        cur = head;
        while (cur != null) {
            Node copyNode = cur.next;
      
            if (cur.random != null) {
                copyNode.random = cur.random.next;
            }
            cur = copyNode.next; 
        }

        cur = head;
        Node copyHead = head.next; 
        while (cur != null) {
            Node copyNode = cur.next;
            cur.next = copyNode.next; 
            if (copyNode.next != null) {
                copyNode.next = copyNode.next.next;
            }
            cur = cur.next;
        }

        return copyHead; 
    } 

148 排序链表

**核心逻辑:**通过 "归并排序分治归并法" 实现链表排序 ------ 利用「归并排序分而治之 + 链表可通过指针拆分 / 合并」的特性,先以快慢指针找链表中点拆分链表,递归排序左右子链表,再合并两个有序子链表,无需额外空间(递归栈除外)且时间复杂度最优,高效完成链表排序。


关键步骤:

边界初始化:处理空链表 / 单节点链表边界情况(head 为 null 或 head.next 为 null 直接返回 );

找中点拆分链表:定义快慢指针找链表中点的前驱节点(快指针为 head.next):

  1. 慢指针步进:slow = slow.next(步速 1);

  2. 快指针步进:fast = fast.next.next(步速 2);

  3. 拆分操作:midPrev.next = null 切断链表,拆分出左子链表(head)和右子链表(midPrev.next);

  4. 递归排序子链表:分别递归排序左子链表(sortList (head))和右子链表(sortList (right));

  5. 合并有序链表:初始化虚拟头节点 dummy,逐位比较两个有序子链表节点值,将较小值节点接入结果链表,拼接剩余节点后返回 dummy.next;

  6. 终止返回:递归逐层合并完成后,返回最终的有序链表头节点。

java 复制代码
    public ListNode sortList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }

        ListNode midPrev = findMidPrev(head);
        ListNode right = midPrev.next;
        midPrev.next = null;

        ListNode leftSorted = sortList(head);
        ListNode rightSorted = sortList(right);

        return mergeTwoSortedLists(leftSorted, rightSorted);
    }

    private ListNode findMidPrev(ListNode head) {
        ListNode slow = head;
        ListNode fast = head.next;
        
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        return slow;
    }

    private ListNode mergeTwoSortedLists(ListNode l1, ListNode l2) {
        ListNode dummy = new ListNode(0);
        ListNode curr = dummy;

        while (l1 != null && l2 != null) {
            if (l1.val <= l2.val) {
                curr.next = l1;
                l1 = l1.next;
            } else {
                curr.next = l2;
                l2 = l2.next;
            }
            curr = curr.next;
        }

        curr.next = (l1 != null) ? l1 : l2;

        return dummy.next;
    }

146 LRU 缓存

核心逻辑: 通过 "哈希表 + 双向链表" 实现 LRU 缓存 ------ 利用哈希表实现 O (1) 时间的键值查找,利用双向链表维护最近使用顺序(头部为最近使用,尾部为最久未使用),每次访问或插入时将节点移到头部,容量不足时删除尾部节点,保证 getput 操作均为 O (1) 平均时间复杂度。


关键步骤:

边界初始化

  1. 定义双向链表节点 Node,包含 keyvalueprevnext

  2. 初始哈希表 cache(存储键:节点映射)、虚拟头节点 head、虚拟尾节点 tail、容量 capacity

  3. 连接 headtail,构建空链表。

get 操作(获取值)

若 key 不存在于 cache,返回 -1;

若存在,取出对应节点 node

  • node 从链表中移除;

  • node 插入到链表头部(标记为最近使用);

  • 返回 node.value

put 操作(插入 / 更新值)

若 key 已存在:

  • 取出节点 node,更新其 value

  • node 从链表中移除,再插入到链表头部;

若 key 不存在:

  • 新建节点 newNode,将其插入链表头部;

  • key → newNode 映射存入哈希表;

  • 若缓存容量超过 capacity

    • 删除链表尾部节点(最久未使用);

    • 从哈希表中移除该节点的 key 映射。

辅助操作(链表维护)

  1. removeNode(Node node):从链表中移除指定节点;

  2. addToHead(Node node):将节点插入到链表头部(head 之后);

  3. removeTail():删除链表尾部节点(tail 之前),并返回该节点。

java 复制代码
class LRUCache {
    class Node {
        int key;
        int value;
        Node prev;
        Node next;
        Node(int key, int value) {
            this.key = key;
            this.value = value;
        }
    }

    private Map<Integer, Node> cache;
    private int capacity;
    private Node head;
    private Node tail;
    private int size;

    public LRUCache(int capacity) {
        this.capacity = capacity;
        this.size = 0;
        this.cache = new HashMap<>();
        
        //虚拟头尾节点
        this.head = new Node(0, 0);
        this.tail = new Node(0, 0);
        head.next = tail;
        tail.prev = head;
    }

    public int get(int key) {
        Node node = cache.get(key);
        if (node == null) {
            return -1;
        }
        // 移动到头部
        removeNode(node);
        addToHead(node);
        return node.value;
    }

    public void put(int key, int value) {
        Node node = cache.get(key);
        if (node == null) {
            // 新建节点
            Node newNode = new Node(key, value);
            cache.put(key, newNode);
            addToHead(newNode);
            size++;
            // 容量不足,删除尾部
            if (size > capacity) {
                Node tailNode = removeTail();
                cache.remove(tailNode.key);
                size--;
            }
        } else {
            // 更新值并移动到头部
            node.value = value;
            removeNode(node);
            addToHead(node);
        }
    }

    // 移除指定节点
    private void removeNode(Node node) {
        node.prev.next = node.next;
        node.next.prev = node.prev;
    }

    // 添加到头部
    private void addToHead(Node node) {
        node.prev = head;
        node.next = head.next;
        head.next.prev = node;
        head.next = node;
    }

    // 删除尾部节点
    private Node removeTail() {
        Node tailNode = tail.prev;
        removeNode(tailNode);
        return tailNode;
    }
}

恭喜你学习完成!✿

相关推荐
weixin_704266052 小时前
[特殊字符] Spring IOC/DI 核心知识点 CSDN 风格总结
java·后端·spring
袋鼠云数栈2 小时前
构建金融级数据防线:数栈 DataAPI 的全生命周期管理实践
java·大数据·数据库·人工智能·api
程序员小明儿2 小时前
量子计算探秘:从零开始的量子编程与算法之旅 · 第五篇
算法·量子计算
灰色小旋风2 小时前
力扣第九题C++回文数
c++·算法·leetcode
常利兵2 小时前
Spring Boot 4.0 牵手RabbitMQ:注解魔法开启消息之旅
spring boot·rabbitmq·java-rabbitmq
拾贰_C2 小时前
[spring boot | springboot web ] spring boot web项目启动失败问题
前端·spring boot·后端
indexsunny2 小时前
互联网大厂Java面试实录:Spring Boot与微服务在电商场景中的应用解析
java·spring boot·面试·kafka·spring security·电商·microservices
用户881586910912 小时前
别再把微服务当银弹了!深度剖析...
spring boot
独自破碎E2 小时前
手撕真题-计算二叉树中两个节点之间的距离
java·开发语言