
链表作为算法面试的必考点今天带你手撕两道超高频经典题LeetCode 19. 删除链表的倒数第 N 个结点LeetCode 24. 两两交换链表中的节点这两道题完美体现了链表解题的核心技巧虚拟头节点 多指针操作。掌握这套思路链表相关题目基本能一通百通全程干货无废话直接上干货 第一题LeetCode 19. 删除链表的倒数第 N 个结点题目描述给你一个链表删除链表的倒数第n个结点并且返回链表的头结点。进阶尝试使用一趟扫描实现。 核心思路快慢指针一次遍历最优解要找到倒数第 n 个节点常规做法是先遍历一次求长度再遍历到对应位置删除两次遍历。但题目要求进阶一次遍历因此我们采用快慢指针技巧核心逻辑如下虚拟头节点兜底创建dummy节点指向head统一处理删除头节点的边界情况所有节点都有前驱节点无需额外判断快指针先行快慢指针均从dummy出发快指针先走n步拉开 n 步间距双指针同步移动快慢指针同时后移直到快指针指向链表末尾节点fast.next null删除目标节点此时慢指针指向待删除节点的前一个节点直接通过slow.next slow.next.next删除即可。完整代码可直接提交/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val val; } * ListNode(int val, ListNode next) { this.val val; this.next next; } * } */ class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { // 1. 虚拟头节点统一处理头节点删除的边界问题 ListNode dummy new ListNode(0); dummy.next head; // 2. 初始化快慢指针均指向虚拟头节点 ListNode fast dummy; ListNode slow dummy; // 3. 快指针先走 n 步拉开间距 for (int i 0; i n; i) { fast fast.next; } // 4. 快慢指针同步移动直到快指针到链表末尾 while (fast.next ! null) { fast fast.next; slow slow.next; } // 5. 删除慢指针的下一个节点倒数第 n 个节点 slow.next slow.next.next; // 6. 返回真实头节点dummy.next 可能与原 head 不同 return dummy.next; } } 代码解析dummy节点的妙用如果直接从head出发当删除的是头节点时没有前驱节点无法直接操作。虚拟头节点让所有节点地位平等逻辑更简洁间距控制快指针先走n步保证最终快慢指针的距离为n快指针到尾时慢指针刚好定位到目标节点前一位删除逻辑slow.next slow.next.next是链表删除的核心操作跳过待删除节点即可。⏱️ 复杂度分析时间复杂度O (L)L 为链表长度仅遍历一次链表空间复杂度O (1)仅使用常数级额外空间。 第二题LeetCode 24. 两两交换链表中的节点题目描述给你一个链表两两交换其中相邻的节点并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题即只能进行节点交换。 核心思路多指针迭代交换断链保护两两交换的核心是修改节点指针指向但必须注意保护后续节点的链接否则会断链丢失数据。步骤如下虚拟头节点初始化同样用dummy节点指向head作为交换操作的前驱起点多指针定义定义 4 个指针分别记录当前操作的前驱节点cur、第一个节点first、第二个节点second、后续节点tmp循环交换条件cur.next ! null cur.next.next ! null保证后续有两个节点可交换核心交换逻辑先保存后续节点tmp→ 重定向cur.next到second→second.next指向first→first.next指向tmp→ 移动cur到first继续下一轮。完整代码可直接提交/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val val; } * ListNode(int val, ListNode next) { this.val val; this.next next; } * } */ class Solution { public ListNode swapPairs(ListNode head) { // 1. 虚拟头节点作为交换操作的前驱起点 ListNode dummy new ListNode(-1); dummy.next head; ListNode cur dummy; // 指向当前操作组的前驱节点 ListNode first, second, tmp; // 2. 循环条件后续有两个节点可交换 while (cur.next ! null cur.next.next ! null) { first cur.next; // 第一个节点 second cur.next.next; // 第二个节点 tmp second.next; // 保存第三个节点防止断链 // 3. 核心交换逻辑修改指针指向 cur.next second; // 前驱节点指向第二个节点 second.next first; // 第二个节点指向第一个节点 first.next tmp; // 第一个节点指向后续节点 // 4. 移动前驱节点处理下一组 cur first; } // 5. 返回交换后的头节点 return dummy.next; } } 代码解析tmp指针的关键作用这是防止断链的核心在修改second.next前必须先保存second.next即第三个节点否则交换后就无法找到后续节点交换顺序必须先让cur.next指向second再让second.next指向first最后让first.next指向tmp顺序绝对不能错循环终止当后续不足两个节点时停止交换符合题目要求。⏱️ 复杂度分析时间复杂度O (L)L 为链表长度仅遍历一次链表空间复杂度O (1)仅使用常数级额外空间。 链表解题核心技巧总结通过这两道题我们提炼出链表操作的万能公式后续所有题目都可套用虚拟头节点dummy是万能钥匙遇到头节点操作删除、交换、插入直接加虚拟头节点边界问题直接消失多指针保存现场修改指针指向时一定要用临时变量保存后续节点断链是链表题的最大杀手循环条件要严谨判断cur.next而非cur避免空指针异常思路大于代码先理清指针的移动逻辑再动手写代码效率翻倍。