《 java 随想录》| LeetCode链表高频考题
前言:这是专门针对java语言讲解的算法解析(题目顺序大致参考《代码随想录》)
思维导图
操作链表
删除节点
删除链表中 D 节点时,只需将其前驱节点 C 的 next 指针指向 D 的下一个节点 E。
添加节点
先让 新节点 F 的 next 指针 指向 C 原来的后继节点 D(保存原有连接,避免链表断裂);
再让 C 节点的 next 指针 指向 新节点 F,完成插入。
链表基础代码
public class ListNode {// 结点的值int val;// 下一个结点ListNode next;// 节点的构造函数(无参)public ListNode() {}// 节点的构造函数(有一个参数)public ListNode(int val) {this.val = val;}// 节点的构造函数(有两个参数)public ListNode(int val, ListNode next) {this.val = val;this.next = next;}
}
LeetCode203. 移除链表元素
一、核心逻辑:通过虚拟头节点统一移除逻辑
链表中每个节点包含值和指向下一节点的引用,要移除值为目标值的节点,可引入虚拟头节点(不存储实际数据),使其作为原头节点的前驱。这样无论是头节点还是中间节点,都能通过 “前驱节点指针调整” 的方式统一处理,避免头节点特殊处理的繁琐。
二、关键细节:虚拟头节点的使用与指针遍历
移除链表元素的难点在于头节点与其他节点处理逻辑不一致,而虚拟头节点可消除这种差异。通过维护一个遍历指针,始终指向当前待检查节点的前驱,就能统一执行 “跳过目标节点” 的操作。
解法:虚拟头节点 + 前驱指针遍历
区间(逻辑范围)含义:以虚拟头节点为起点,遍历指针 prev
始终指向当前处理节点的前驱,待检查范围是 prev.next
及之后的节点。
循环条件:while (prev.next != null)
因为只要 prev
的下一个节点存在,就需要检查其是否为目标值,循环继续。
指针调整:
- 若
prev.next.val == val
:说明下一个节点是目标节点,需移除,因此将prev.next
指向prev.next.next
(跳过目标节点)。 - 若
prev.next.val != val
:说明下一个节点无需移除,prev
后移一位(prev = prev.next
),继续检查后续节点。
返回结果:循环结束后,所有目标节点已被移除,返回虚拟头节点的下一个节点(即新的头节点)
代码示例:
public ListNode removeElements(ListNode head, int val) {while(head!=null && head.val==val) {head = head.next;}ListNode curr = head;while(curr!=null && curr.next !=null) {if(curr.next.val == val){curr.next = curr.next.next;} else {curr = curr.next;}}return head;
}
LeetCode206. 反转链表
如果再定义一个新的链表,实现链表元素的反转,其实这是对内存空间的浪费。
其实只需改变链表的next指针的指向,直接将链表反转 ,而非重新定义一个新的链表,如图:
我们拿有示例中的链表来举例,如动画所示:
- 初始化指针:cur 指向头节点,pre 初始化为 null,tmp 用于临时保存节点。
- 当 cur 不为 null 时,执行以下操作:
- 保存下一个节点:用 tmp 存储 cur 的 next 节点(避免后续指向改变后丢失)。
- 反转当前节点指向:将 cur 的 next 指向 pre。
- 移动指针:pre 移至 cur 位置,cur 移至 tmp 位置(即之前保存的下一个节点)。
- 结果处理:循环结束后,pre 指向反转后链表的新头节点,返回 pre。
代码实现:
// 双指针
class Solution {public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode cur = head;ListNode temp = null;while (cur != null) {temp = cur.next;// 保存下一个节点cur.next = prev;prev = cur;cur = temp;}return prev;}
LeetCode24. 两两交换链表中的节点
这道题目正常模拟就可以了。
建议使用虚拟头结点,这样会方便很多,要不然每次针对头结点(没有前一个指针指向头结点),还要单独处理。
接下来就是交换相邻两个元素了,此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序
初始时,cur指向虚拟头结点,然后进行如下三步:
操作之后,链表如下:
看这个可能就更直观一些了:
代码实现:
public ListNode swapPairs(ListNode head) {ListNode dummy = new ListNode(0, head);ListNode cur = dummy;while (cur.next != null && cur.next.next != null) {ListNode node1 = cur.next;// 第 1 个节点ListNode node2 = cur.next.next;// 第 2 个节点cur.next = node2; // 步骤 1node1.next = node2.next;// 步骤 3node2.next = node1;// 步骤 2cur = cur.next.next;}return dummy.next;
}
LeetCode19. 删除倒数第n个节点
双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了。
分为如下几步:
定义fast指针和slow指针,初始值为虚拟头结点,如图:
fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
fast和slow同时移动,直到fast指向末尾,如题:
删除slow指向的下一个节点,如图:
代码实现:
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {//新建一个虚拟头节点指向headListNode dummyNode = new ListNode(0);dummyNode.next = head;//快慢指针指向虚拟头节点ListNode fastIndex = dummyNode;ListNode slowIndex = dummyNode;// 只要快慢指针相差 n 个结点即可for (int i = 0; i <= n; i++) {fastIndex = fastIndex.next;}while (fastIndex != null) {fastIndex = fastIndex.next;slowIndex = slowIndex.next;}// 此时 slowIndex 的位置就是待删除元素的前一个位置。// 具体情况可自己画一个链表长度为 3 的图来模拟代码来理解// 检查 slowIndex.next 是否为 null,以避免空指针异常if (slowIndex.next != null) {slowIndex.next = slowIndex.next.next;}return dummyNode.next;}
}