LeetCode hot 100 每日一题(18)——206.反转链表
迭代算法
class Solution {public ListNode reverseList(ListNode head) {// 如果链表为空 或者只有一个节点,直接返回即可if (head == null || head.next == null) {return head;}// 定义三个指针ListNode pre = null; // pre 指向当前节点的前一个节点(初始时为 null)ListNode cur = head; // cur 指向当前处理的节点(初始时是 head)ListNode next = head.next; // next 指向 cur 的下一个节点,防止链表断开// 遍历整个链表while (cur != null) {// 1. 断开并反转:让当前节点指向前一个节点cur.next = pre;// 2. 移动 pre 到 cur 位置(相当于前进一格)pre = cur;// 3. 移动 cur 到 next(继续处理下一个节点)cur = next;// 4. 更新 next 指针(如果 cur 不是 null,就让 next 前进一格)if (next != null) {next = next.next;}}// 最终 pre 指向新链表的头结点return pre;}
}
图解说明(以链表 1 → 2 → 3 → 4 → 5 为例)
初始状态:
pre = null
cur = 1
next = 2
链表: 1 → 2 → 3 → 4 → 5
第一次循环:
cur.next = pre
→ 让 1 指向 null
pre = cur
→ pre 移动到 1
cur = next
→ cur 移动到 2
next = next.next
→ next 移动到 3
pre = 1 → null
cur = 2
next = 3
链表断开后局部: 1 → null 剩余: 2 → 3 → 4 → 5
第二次循环:
cur.next = pre
→ 让 2 指向 1
pre = cur
→ pre 移动到 2
cur = next
→ cur 移动到 3
next = next.next
→ next 移动到 4
pre = 2 → 1 → null
cur = 3
next = 4
链表断开后局部: 2 → 1 → null
剩余: 3 → 4 → 5`
第三次循环:
pre = 3 → 2 → 1 → null
cur = 4
next = 5
第四次循环:
pre = 4 → 3 → 2 → 1 → null
cur = 5
next = null
第五次循环:
pre = 5 → 4 → 3 → 2 → 1 → null
cur = null // 结束
最终返回 pre
,即新的头结点 5。
-
pre
负责记录“反转好的部分”; -
cur
负责扫描链表; -
next
保证链表不会断裂。
整个过程就像不断地把“火车车厢”从前面摘下来,挂到新的链表头部。
难以理解的是,cur.next = pre
可以让链表断开,随后三个指针依次向后移动即可
递归解法
递归实现反转链表常常用来考察递归思想,这里就用纯递归来翻转链表。
具体来说,我们的 reverse
函数定义是这样的:
输入一个节点 head
,将「以 head
为起点」的链表反转,并返回反转之后的头结点。
/*** 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 reverseList(ListNode head) {// ① 递归的终止条件:// 当链表为空(head == null)或只剩一个节点(head.next == null)时,// 这个节点本身就是反转后的头节点,直接返回它。if (head == null || head.next == null) {return head;}// ② 递归处理子问题:// 反转以 head.next 为头的“子链表”,// 递归返回值 last 是整个子链表反转后的新头(也就是原来的尾)。ListNode last = reverseList(head.next);// ③ 回溯阶段指针翻转(关键两步中的第一步):// 此时 head.next 指向“子链表反转后的尾结点前的那个结点”(原来的 head.next),// 让该结点的 next 指回 head,把 head 接到子链表的末尾。head.next.next = head;// ④ 断开旧的指向(关键两步中的第二步):// 原来 head -> head.next 的旧边需要置空,否则会形成环。head.next = null;// ⑤ 返回整条链反转后的头结点:// 始终是递归返回的 last。return last;}
}
图解
原始链表
ListNode last = reverse(head.next);
执行reverse后变成以下:
head.next.next = head;
head.next = null;
return last;
原来 head -> head.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; }* }*/
-
public class ListNode { ... }
定义了一个叫 ListNode 的类,每个对象就是链表中的一个节点。 -
int val;
节点存储的 值(比如链表里 1、2、3 就存在这里)。 -
ListNode next;
指向 下一个节点 的引用。如果是链表的最后一个节点,它的next
就是null
。
接下来是三个 构造方法(方便创建不同情况的节点):
-
ListNode() {}
空构造器,创建一个值为默认值(0),且没有下一个节点的对象。
(几乎用不到,但保留了。) -
ListNode(int val) { this.val = val; }
创建一个只带值的节点,比如:ListNode node = new ListNode(5); // val = 5, next = null
-
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
创建一个带值、同时直接指定下一个节点的对象,比如:ListNode node2 = new ListNode(2); ListNode node1 = new ListNode(1, node2); // node1 -> node2