链表类力扣刷题总结
目录
一、链表基础原理
链表的分类
链表的特性
二、核心解题方法
1. 双指针法
2. 递归法
3. 哈希表法
4. 哑节点(虚拟头节点)
三、经典题型分类与代码实现
(一)基础操作类
1. 反转链表(LeetCode 206)
2. 合并两个有序链表(LeetCode 21)
3. 删除链表的倒数第 N 个节点(LeetCode 19)
(二)环与相交类
1. 环形链表(LeetCode 141)
2. 环形链表 II(LeetCode 142)
3. 相交链表(LeetCode 160)
(三)回文与对称类
回文链表(LeetCode 234)
(四)排序与复杂重组类
1. 排序链表(LeetCode 148)
2. K 个一组翻转链表(LeetCode 25)
3. 随机链表的复制(LeetCode 138)
(五)数值计算类
两数相加(LeetCode 2)
(六)困难拓展类:合并 K 个升序链表(LeetCode 23)
总结
一、链表基础原理
链表是一种线性数据结构,由 ** 节点(Node)** 组成,每个节点包含两部分:
- 数据域:存储节点的值(如
int val)。- 指针域:存储下一个节点的引用(如
ListNode next)。
链表的分类
- 单链表:每个节点只有一个指向下一节点的指针,尾节点指向
null。- 双链表:每个节点有两个指针(
prev指向前一节点,next指向后一节点)。- 循环链表:尾节点指向头节点,形成 “环”。
链表的特性
- 插入 / 删除高效:已知位置时,时间复杂度为
O(1)(仅需修改指针)。- 访问低效:访问第
k个节点需遍历,时间复杂度O(n)。
二、核心解题方法
1. 双指针法
- 快慢指针:快指针速度是慢指针的 2 倍(或其他倍数),用于:
- 找链表中点(如回文链表、排序链表的归并)。
- 判断环形链表(相遇则有环)。
- 找环形链表的入环点(相遇后,一个指针回
head,同速移动)。
- 前后指针:两个指针一前一后,用于:
- 反转链表(
prev和curr配合修改指针方向)。 - 删除倒数第 N 个节点(快指针先走
N步,再一起走)。
- 反转链表(
2. 递归法
递归的本质是将大问题拆解为相同逻辑的子问题,适用于:
- 反转链表(反转剩余部分,再连接当前节点)。
- 合并有序链表(比较头节点,递归合并剩余部分)。
3. 哈希表法
用于记录节点的 “访问状态” 或 “映射关系”,适用于:
- 判断环形链表(记录已访问节点,重复则有环)。
- 相交链表(记录 A 链表节点,遍历 B 时判断是否存在)。
- 随机链表的复制(记录原节点到新节点的映射,处理
random指针)。
4. 哑节点(虚拟头节点)
当链表的头节点操作需要特殊处理时(如合并链表时头节点不确定、删除头节点),创建一个 val 无意义的节点作为头节点的前驱,简化逻辑。通常是dummy~
三、经典题型分类与代码实现
(一)基础操作类
1. 反转链表(LeetCode 206)
问题:反转单链表(如 1->2->3->null → 3->2->1->null)。
方法:双指针或递归。
注:此处的null也可以是其他节点;用于反转部分节点
// 双指针法
public ListNode reverseList(ListNode head) {// prev通常是链表尾的指向ListNode prev = null;// 当前节点,后面逐渐后移,至nullListNode curr = head;while (curr != null) {//保存当前节点的下一个节点,避免后续当前节点的移动ListNode nextTemp = curr.next;//将当前指向prev表尾curr.next = prev;// prev指针左移/前移prev = curr;//当前指针后移,进行下一轮交换curr = nextTemp;}//最终prev从最右端移到了最左端,成为了头节点return prev;
}// 递归法
public ListNode reverseList(ListNode head) {// 递归终止条件if (head == null || head.next == null) return head;//1->2->3->4->nullListNode newHead = reverseList(head.next);head.next.next = head;head.next = null;return newHead;
}/**
递归处理 “剩余部分”ListNode newHead = reverseList(head.next);
以链表 1->2->3->4->null 为例,递归调用会不断深入:
第一次调用:head=1,处理 head.next=2 → 进入下一层递归。
第二次调用:head=2,处理 head.next=3 → 进入下一层递归。
第三次调用:head=3,处理 head.next=4 → 进入下一层递归。
第四次调用:head=4,满足 head.next == null → 触发终止条件,返回 4(此时 newHead=4)。
步骤 3:调整 “当前节点” 与 “后继” 的指针head.next.next = head;
head.next = null;
回到第三次调用(head=3,newHead=4):
head.next 是 4,所以 head.next.next = head → 等价于 4.next = 3,此时链表变为 3<->4。
head.next = null → 断开 3->4 的旧连接,最终 3 的 next 为 null,链表变为 4->3->null。
返回 newHead=4 → 回到第二次调用。
回到第二次调用(head=2,newHead=4):
head.next 是 3,所以 3.next = 2 → 链表变为 4->3->2。
head.next = null → 断开 2->3 的旧连接,2 的 next 为 null,链表变为 4->3->2->null。
返回 newHead=4 → 回到第一次调用。
回到第一次调用(head=1,newHead=4):
head.next 是 2,所以 2.next = 1 → 链表变为 4->3->2->1。
head.next = null → 断开 1->2 的旧连接,1 的 next 为 null,链表变为 4->3->2->1->null。
返回 newHead=4 → 最终整个链表完成反转。**/
2. 合并两个有序链表(LeetCode 21)
问题:将两个升序链表合并为一个升序链表。
方法:双指针 + 哑节点。
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {// 设置哑节点,因为两个头节点都要被处理ListNode dummy = new ListNode(0);// 设置当前节点,后续不断后移ListNode prev = dummy;// 当两个链表都不为空时继续合并while (list1 != null && list2 != null) {// 将值更小的节点优先加入,同时移动结果链表和当前链表if (list1.val <= list2.val) {// 实现连接prev.next = list1;list1 = list1.next;} else {prev.next = list2;list2 = list2.next;}prev = prev.next;}// 哪个表空了就直接连上,注意是在循环结束后prev.next = list1 != null ? list1 : list2;// 返回哑节点的next,刚好是头节点return dummy.next;
}
3. 删除链表的倒数第 N 个节点(LeetCode 19)
问题:删除链表的倒数第 n 个节点(如 1->2->3->4->5,n=2 → 1->2->3->5)。
方法:快慢指针 + 哑节点(快指针先走 n 步,再一起走,找到待删除节点的前驱)。
public ListNode removeNthFromEnd(ListNode head, int n) {// 哑节点构造的简化写法,和 “先 new 哑节点再设置dummy.next = head” 效果完全一致// ListNode dummy = new ListNode(0); dummy.next = head;ListNode dummy = new ListNode(0, head);// 初始化,都先在哑节点启动ListNode fast = dummy, slow = dummy;// 快节点先走n+1步(此处如果是i<n就说多走n步,while循环条件需要改为fast.next!=null)for (int i = 0; i <= n; i++) fast = fast.next;while (fast != null) {// 两个指针同时移动,最终fast到达null,slow比fast落后n+1个位置,在需要删除的节点前驱fast = fast.next;slow = slow.next;}// 此处对第n个节点进行删除slow.next = slow.next.next;//返回头节点return dummy.next;
}
(二)环与相交类
1. 环形链表(LeetCode 141)
问题:判断链表是否有环。
方法:快慢指针(快指针走 2 步,慢指针走 1 步,相遇则有环)。
public boolean hasCycle(ListNode head) {if (head == null || head.next == null) return false;ListNode slow = head, fast = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) return true;}return false;
}
注:哈希表法也可以解决
public ListNode removeNthFromEnd(ListNode head, int n) {if (head == null) {return null;}// 第一步:遍历链表,用Map记录每个节点的索引和节点本身Map<Integer, ListNode> indexToNode = new HashMap<>();ListNode current = head;int length = 0; // 链表总长度while (current != null) {indexToNode.put(length, current); // 索引从0开始current = current.next;length++;}// 第二步:计算要删除的节点的索引(倒数第n个 = 正数第 length - n 个)int deleteIndex = length - n;// 特殊情况:删除的是头节点(索引0)if (deleteIndex == 0) {return head.next;}// 第三步:找到目标节点的前一个节点,删除目标节点ListNode prevNode = indexToNode.get(deleteIndex - 1); // 前一个节点的索引prevNode.next = prevNode.next.next; // 跳过目标节点return head;
2. 环形链表 II(LeetCode 142)
问题:返回环形链表的入环第一个节点。
方法:快慢指针相遇后,一个指针回 head,同速移动,相遇处为入环点。
public ListNode detectCycle(ListNode head) {if (head == null || head.next == null) return null;ListNode slow = head, fast = head;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;if (slow == fast) { // 相遇,存在环ListNode ptr = head;while (ptr != slow) {ptr = ptr.next;slow = slow.next;}return ptr; // 入环点}}return null;
}
3. 相交链表(LeetCode 160)
问题:找到两个单链表相交的起始节点(不相交返回 null)。
方法:双指针(A 走完走 B,B 走完走 A,相遇则相交)。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if (headA == null || headB == null) return null;ListNode pA = headA, pB = headB;while (pA != pB) {pA = pA == null ? headB : pA.next;pB = pB == null ? headA : pB.next;}return pA;
}
(三)回文与对称类
回文链表(LeetCode 234)
问题:判断链表是否为回文(如 1->2->2->1 是回文,1->2 不是)。
方法:找中点 + 反转后半段 + 比较。
public boolean isPalindrome(ListNode head) {if (head == null || head.next == null) return true;// 找中点ListNode slow = head, fast = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next;}// 反转后半段ListNode reversedHalf = reverseList(slow.next);// 比较前半段和反转后的后半段ListNode p1 = head, p2 = reversedHalf;while (p2 != null) {if (p1.val != p2.val) return false;p1 = p1.next;p2 = p2.next;}return true;
}private ListNode reverseList(ListNode head) {ListNode prev = null, curr = head;while (curr != null) {ListNode nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}return prev;
}
(四)排序与复杂重组类
1. 排序链表(LeetCode 148)
问题:对链表进行升序排序。
方法:归并排序(找中点 + 递归合并)。
public ListNode sortList(ListNode head) {if (head == null || head.next == null) return head;// 找中点ListNode slow = head, fast = head.next;while (fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}ListNode mid = slow.next;slow.next = null; // 拆分链表// 递归排序左右两部分ListNode left = sortList(head);ListNode right = sortList(mid);// 合并ListNode dummy = new ListNode(0);ListNode prev = dummy;while (left != null && right != null) {if (left.val <= right.val) {prev.next = left;left = left.next;} else {prev.next = right;right = right.next;}prev = prev.next;}prev.next = left != null ? left : right;return dummy.next;
}
2. K 个一组翻转链表(LeetCode 25)
问题:每 k 个节点一组进行翻转(如 1->2->3->4->5,k=2 → 2->1->4->3->5)。
方法:分组处理,每组内部反转,再拼接。
public ListNode reverseKGroup(ListNode head, int k) {ListNode dummy = new ListNode(0, head);ListNode prev = dummy;while (true) {// 检查剩余节点是否足够k个ListNode tail = prev;for (int i = 0; i < k; i++) {tail = tail.next;if (tail == null) return dummy.next;}ListNode nextGroup = tail.next;// 反转当前k个节点ListNode[] reversed = reverse(prev.next, tail);ListNode newHead = reversed[0], newTail = reversed[1];// 拼接prev.next = newHead;newTail.next = nextGroup;prev = newTail;}
}// 反转从head到tail的链表,返回新的头和尾
private ListNode[] reverse(ListNode head, ListNode tail) {ListNode prev = tail.next, curr = head;while (prev != tail) {ListNode nextTemp = curr.next;curr.next = prev;prev = curr;curr = nextTemp;}return new ListNode[]{tail, head};
}
3. 随机链表的复制(LeetCode 138)
问题:复制一个带有 random 指针的链表(random 可能指向任意节点或 null)。
方法:哈希表记录原节点到新节点的映射,先复制 next 指针,再处理 random 指针。
public Node copyRandomList(Node head) {if (head == null) return null;Map<Node, Node> map = new HashMap<>();Node curr = head;// 复制节点,存储映射while (curr != null) {map.put(curr, new Node(curr.val));curr = curr.next;}curr = head;// 处理next和random指针while (curr != null) {map.get(curr).next = map.get(curr.next);map.get(curr).random = map.get(curr.random);curr = curr.next;}return map.get(head);
}
(五)数值计算类
两数相加(LeetCode 2)
问题:两个非空链表表示两个非负整数(每位数字逆序存储),求它们的和。
方法:模拟加法,注意进位,哑节点存储结果。
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode curr = dummy;int carry = 0;while (l1 != null || l2 != null || carry != 0) {int sum = carry;if (l1 != null) {sum += l1.val;l1 = l1.next;}if (l2 != null) {sum += l2.val;l2 = l2.next;}carry = sum / 10;curr.next = new ListNode(sum % 10);curr = curr.next;}return dummy.next;
}
(六)困难拓展类:合并 K 个升序链表(LeetCode 23)
问题:合并 k 个升序链表,返回合并后的升序链表。
方法:优先队列(最小堆)或分治合并。
// 方法1:优先队列(最小堆)
public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) return null;PriorityQueue<ListNode> pq = new PriorityQueue<>((a, b) -> a.val - b.val);for (ListNode list : lists) {if (list != null) pq.offer(list);}ListNode dummy = new ListNode(0);ListNode curr = dummy;while (!pq.isEmpty()) {ListNode node = pq.poll();curr.next = node;curr = curr.next;if (node.next != null) pq.offer(node.next);}return dummy.next;
}// 方法2:分治合并
public ListNode mergeKLists(ListNode[] lists) {if (lists == null || lists.length == 0) return null;return merge(lists, 0, lists.length - 1);
}private ListNode merge(ListNode[] lists, int left, int right) {if (left == right) return lists[left];int mid = left + (right - left) / 2;ListNode l1 = merge(lists, left, mid);ListNode l2 = merge(lists, mid + 1, right);return mergeTwoLists(l1, l2);
}private ListNode mergeTwoLists(ListNode l1, ListNode l2) {ListNode dummy = new ListNode(0);ListNode prev = dummy;while (l1 != null && l2 != null) {if (l1.val <= l2.val) {prev.next = l1;l1 = l1.next;} else {prev.next = l2;l2 = l2.next;}prev = prev.next;}prev.next = l1 != null ? l1 : l2;return dummy.next;
}
总结
链表题的核心在于指针的灵活操作。需掌握 “双指针、递归、哈希表、哑节点” 这四类方法,将大部分题型拆解为 “模板化” 的逻辑。
- 环和相交问题:快慢指针的 “相遇逻辑”。
- 反转和合并:双指针 + 哑节点的 “拼接模板”。
- 复杂重组(如 K 个一组、排序):将问题拆分为 “子问题”(递归或分组),再逐一解决。

