LeetCode - 反转链表 / K 个一组翻转链表
欢迎光临小站:致橡树
反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
示例 1:
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
示例 2:
输入:head = [1,2]
输出:[2,1]
示例 3:
输入:head = []
输出:[]
提示:
-
链表中节点的数目范围是
[0, 5000]
-
-5000 <= Node.val <= 5000
进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题?
解题思路
递归(尾插法)
递归递归,有递有归。
我们先「递」到链表的末尾节点,作为新链表的头节点。然后在「归」的过程中,一个一个地把节点插在新链表的末尾。
递归的基本思想
递归的核心在于将问题分解为更小的子问题,直到达到基本情况(base case),然后利用子问题的解构建原问题的解。
应用于链表反转
对于链表反转问题,我们可以这样思考:
-
基本情况:当链表为空或只有一个节点时,反转后的链表就是它本身。
-
递归步骤:假设我们已经能够反转除第一个节点外的剩余链表,然后只需要将第一个节点连接到已反转链表的末尾。
代码解析
public ListNode reverseList(ListNode head) {if (head == null) {return head; // 处理空链表的情况}return doReverseList(head); // 调用实际的递归函数
}public ListNode doReverseList(ListNode head) {// 基本情况:当前节点是最后一个节点if (head.next == null) {return head; // 返回这个节点作为新链表的头}// 递归反转剩余链表ListNode tail = doReverseList(head.next);// 关键操作:将当前节点连接到已反转链表的末尾ListNode newHead = head.next; // newHead实际上是原链表的下一个节点newHead.next = head; // 将当前节点接在已反转链表的后面head.next = null; // 断开原来的连接,防止循环return tail; // 返回新链表的头节点(即原链表的尾节点)
}
具体步骤说明
-
终止条件:当
head.next == null
时,说明当前节点是最后一个节点,直接返回它作为新链表的头。 -
递归调用:
doReverseList(head.next)
会反转从head.next
开始的链表,并返回反转后的头节点(即原链表的尾节点)。 -
连接当前节点:
-
newHead = head.next
:获取已反转链表的最后一个节点(即原链表中当前节点的下一个节点)。 -
newHead.next = head
:将当前节点接在已反转链表的后面。 -
head.next = null
:断开当前节点与原链表的连接,防止循环。
-
-
返回结果:最终返回的是原链表的尾节点,即反转后的新链表的头节点。
总结
这种递归解法简洁优雅,但需要注意:
-
递归深度可能引发栈溢出(对于非常长的链表)。
-
理解递归的关键在于信任递归函数能正确解决子问题,并专注于当前层的逻辑。
迭代(头插法)
核心思想
像翻书一样,每次只处理当前页(节点),把它的指向从后页改为前页,直到翻完全书(链表)。如果确实理解起来比较抽象,可以硬背下来,代码是比较固定的。
变量作用
-
prev
:记录已反转部分的头节点(初始为null
) -
curr
:当前待反转的节点(初始为head
) -
next
:临时保存curr
的下一个节点
四步操作流程
-
备份后驱:
next = curr.next
(先记住下一个要处理的节点,避免断链后丢失) -
反转指针:
curr.next = prev
(将当前节点指向前一个节点,实现反转) -
移动prev:
prev = curr
(已反转部分的新头变成当前节点) -
移动curr:
curr = next
(继续处理下一个节点)
示例演示
初始状态:
null [A] → [B] → [C] → null
pre head
第1次循环:
-
next = B
(记住B的位置) -
A.next = null
(A指向null) -
pre = A
(已反转部分:A→null) -
head = B
(准备处理B)
null ← [A] [B] → [C] → nullpre head
第2次循环:
-
next = C
(记住C的位置) -
B.next = A
(B指向A) -
pre = B
(已反转部分:B→A→null) -
head = C
(准备处理C)
null ← [A] ← [B] [C] → nullpre head
第3次循环:
-
next = null
(记住null位置) -
C.next = B
(C指向B) -
pre = C
(已反转部分:C→B→A→null) -
head = null
(处理完成)
null ← [A] ← [B] ← [C]
完整代码
class Solution {public ListNode reverseList(ListNode head) {ListNode pre = null;while(head!=null){ListNode next = head.next;head.next = pre;pre = head;head = next;}return pre;}
}
K 个一组翻转链表
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[2,1,4,3,5]
示例 2:
输入:head = [1,2,3,4,5], k = 3
输出:[3,2,1,4,5]
提示:
-
链表中的节点数目为
n
-
1 <= k <= n <= 5000
-
0 <= Node.val <= 1000
解题思路
-
整体思路
-
特殊情况处理:如果k=1,不需要反转,直接返回原链表
-
创建虚拟头节点dummy:用于简化操作,最终返回dummy.next
-
遍历链表:每次处理一组K个节点
-
检查剩余节点是否足够K个:
-
从当前head开始向后遍历k-1次
-
如果中途遇到null,说明剩余节点不足k个
-
-
处理两种情况:
-
如果剩余节点足够k个:反转这k个节点
-
如果不足k个:保持这组节点顺序不变
-
-
将处理后的子链表连接到结果链表
关键方法解析
1.
reverseKGroup
方法-
dummy节点:作为结果链表的虚拟头节点
-
cur指针:始终指向结果链表的末尾
-
外层while循环:遍历原始链表
-
内层while循环:检查剩余节点是否足够k个
-
flag标志:标记是否成功找到k个节点
2.
doReverse
方法-
反转从head开始的k个节点
-
使用迭代法反转:
-
维护newHead作为反转后的头节点
-
逐个节点反转指针方向
-
每次处理一个节点,k减1
-
代码执行流程示例
以链表1→2→3→4→5,k=3为例:
-
第一组1→2→3:
-
检查足够3个节点
-
反转得到3→2→1
-
连接到dummy后面
-
-
第二组4→5:
-
检查发现只有2个节点(<k)
-
不反转,直接连接到结果链表末尾
-
最终结果:3→2→1→4→5
-
完整代码
class Solution {public ListNode reverseKGroup(ListNode head, int k) {ListNode dummy = new ListNode(-1);ListNode cur = dummy;while (head != null) {ListNode start = head;int n = k;boolean flag = true;while (n > 0) {head = head.next;if (head == null) {flag = false;break;}n--;}while (cur.next != null) {cur = cur.next;}if (flag) {cur.next = doReverse(start, k);} else {cur.next = start;}}return dummy.next;}public ListNode doReverse(ListNode head, int k) {if (k == 1) {return head;}ListNode cur = head;ListNode newHead = null;while (k > 0 && cur != null) {ListNode next = cur.next;cur.next = newHead;newHead = cur;cur = next;k--;}return newHead;}public static void main(String[] args) {Solution solution = new Solution();ListNode head = new ListNode(1);head.next = new ListNode(2);head.next.next = new ListNode(3);head.next.next.next = new ListNode(4);head.next.next.next.next = new ListNode(5);ListNode listNode = solution.reverseKGroup(head, 5);System.out.println();}
}