)
1. 题目简述题目链接给你链表的头节点 head每 k 个节点一组进行翻转请你返回修改后的链表。k 是一个正整数它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍那么请将最后剩余的节点保持原有顺序。限制你不能只是单纯地改变节点内部的值而是需要实际进行节点交换。2. 核心实现方法迭代 分组反转这道题的最优解法是使用迭代法配合虚拟头节点 (Dummy Node)。整体思路分为两步分组探路从当前组的起点向后走k步如果能走满k步说明这一组需要翻转如果不足k步说明剩余节点保持原序即可直接结束。原地反转并拼接在组内进行标准的链表反转但需要注意将反转后的尾部与下一组的头部重新连接。3.推演 (k3)为了彻底讲清楚反转过程我们避开简单的k2采用一个更复杂的例子链表1 - 2 - 3 - 4 - 5k 33.1 初始状态与指针定义我们先定义几个关键指针dummy虚拟头节点指向 1。groupPrev指向当前待翻转组的前一个节点。初始时 groupPrev dummy。kth用来探路寻找当前组的第k个节点。groupNext当前组翻转后应该连接的下一个节点。初始链表状态dummy - 1 - 2 - 3 - 4 - 5 - nullgroupPrev 指向 dummy。3.2 第一轮循环翻转 [1, 2, 3]Step 1: 探路寻找第 k 个节点从 groupPrev 出发向后走 3 步找到 kth 3。此时groupNext kth-next 4。记录下下一组的起点Step 2: 核心反转逻辑我们使用 prev 和 curr 两个指针进行局部反转。关键技巧让 prev 初始指向 groupNext即 4curr 初始指向当前组的头节点即 1。这样做的目的是在反转的过程中顺带把当前组的尾节点直接连到下一组的头上省去后续的拼接步骤。初始prev 4, curr 1第 1 次循环temp curr-next (temp 2)curr-next prev (1 - 4)prev curr (prev 1)curr temp (curr 2)此时局部状态1 - 42和3还没处理。第 2 次循环temp curr-next (temp 3)curr-next prev (2 - 1)prev curr (prev 2)curr temp (curr 3)此时局部状态2 - 1 - 4。第 3 次循环temp curr-next (temp 4即 groupNext)curr-next prev (3 - 2)prev curr (prev 3)curr temp (curr 4)此时局部状态3 - 2 - 1 - 4。Step 3: 循环结束与拼接当 curr groupNext (即 4) 时反转循环结束。此时prev 指向了反转后的新头节点 3。将前驱节点连上新头groupPrev-next prev; (dummy - 3)更新 groupPrev 为下一轮的前驱节点groupPrev temp; (注意这里的 temp 是第一次循环前保存的 1即反转后的尾节点)。groupPrev 现在指向1。第一轮结束后的链表状态dummy - 3 - 2 - 1 - 4 - 5 - nullgroupPrev 指向 1。3.3 第二轮循环处理剩余节点 [4, 5]Step 1: 探路失败从 groupPrev (即 1) 出发向后走 3 步第 1 步到 4第 2 步到 5第 3 步发现 kth 变成了 nullptr。因为不足 3 个节点说明剩余节点不需要翻转。Step 2: 退出循环直接 break 跳出大循环。3.4 最终结果返回 dummy-next得到最终链表3 - 2 - 1 - 4 - 54. 代码实现/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* reverseKGroup(ListNode* head, int k) { // 创建虚拟头节点避免处理头节点翻转的特殊情况 ListNode* dummy new ListNode(0); dummy-next head; // groupPrev 指向当前要翻转的 k 个节点的前一个节点 ListNode* groupPrev dummy; while (true) { // 1. 检查剩余节点是否足够 k 个 ListNode* kth groupPrev; for (int i 0; i k kth ! nullptr; i) { kth kth-next; } // 如果不足 k 个说明后面的不用翻转了直接退出 if (kth nullptr) { break; } // 2. 记录下一组的起始节点 ListNode* groupNext kth-next; // 3. 翻转当前组的 k 个节点 // 核心技巧prev 初始指向 groupNext方便反转后直接连上后面 ListNode* prev groupNext; ListNode* curr groupPrev-next; while (curr ! groupNext) { ListNode* temp curr-next; curr-next prev; prev curr; curr temp; } // 4. 连接翻转后的部分与前面的链表 ListNode* temp groupPrev-next; // 原来的头节点现在是尾节点 groupPrev-next prev; // 前驱节点指向新头 groupPrev temp; // groupPrev 移动到当前组的尾部准备下一轮 } return dummy-next; } };5. 复杂度分析时间复杂度O(N)。每个节点最多被遍历两次一次是探路寻找 kth一次是反转操作整体呈线性时间。空间复杂度O(1)。只使用了常数个额外的指针变量满足题目“实际进行节点交换”且不消耗额外空间的要求。