一、题目完整解读
题目描述
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
示例
| 输入 | 输出 | 解释 |
|---|---|---|
head = [1,2,3,4,5], k = 2 |
[4,5,1,2,3] |
向右移动 2 步,最后 2 个节点移到头部 |
head = [0,1,2], k = 4 |
[2,0,1] |
链表长度 3,4%3=1,向右移动 1 步 |
head = [1,2], k = 2 |
[1,2] |
移动整倍数步,链表不变 |
核心要求
- 时间复杂度:O (n)(仅遍历链表常数次)
- 空间复杂度:O (1)(仅使用常数个指针,不额外开辟空间)
二、三次反转法核心思路
右旋转 k 步的本质是「将链表最后 k 个节点整体移到头部」,利用链表反转的特性,通过三次反转实现该逻辑:
- 反转整个链表 :把最后
k个节点翻到链表头部(顺序反转); - 反转前
k个节点 :恢复这k个节点的原有顺序; - 反转剩余
len-k个节点:恢复剩余节点的原有顺序。
逻辑验证(以 [1,2,3,4,5], k=2 为例)
- 原链表:
1→2→3→4→5 - 反转整个链表:
5→4→3→2→1(最后 2 个节点4,5变前 2 个) - 反转前 2 个节点:
4→5→3→2→1(恢复4,5顺序) - 反转剩余 3 个节点:
4→5→1→2→3(恢复1,2,3顺序) - 最终结果:
4→5→1→2→3(符合预期)
三、踩坑过程与修复(从错误到正确)
版本 1:初始错误代码(典型问题汇总)
cpp
class Solution {
public:
void reverseList(ListNode* head,int left,int right){
ListNode dummy(0,head);
ListNode* p0=&dummy;
for(int i=1;i<left;++i){
p0=p0->next;
}
ListNode* p1=nullptr;
ListNode* p2=p0->next;
// 坑1:反转循环次数不足
for(int i=1;i<right-left;++i){
ListNode* p3=p2->next;
p2->next=p1;
p1=p2;
p2=p3;
}
p0->next->next=p2;
p0->next=p1;
// 坑2:局部变量修改,外部指针无变化
head=dummy.next;
}
ListNode* rotateRight(ListNode* head, int k) {
// 坑3:长度计算错误(初始1,多算1)
int len=1;
ListNode* l=head;
while(l){
len++;
l=l->next;
}
// 坑4:未处理k>len,无取模
reverseList(head,1,len);
reverseList(head,1,k);
// 坑5:反转区间计算错误(len-k而非k+1)
reverseList(head,len-k,len);
return head;
}
};
核心坑点拆解
| 坑点 | 具体问题 | 后果 |
|---|---|---|
| 反转函数参数错误 | reverseList 的 head 是值传递 |
函数内修改 head 仅改变局部变量,外部链表无变化 |
| 反转循环次数错误 | i<right-left(少执行 1 次) |
区间反转不完整,链表顺序混乱 |
| 长度计算错误 | len=1 初始化,while(l) 内直接加 1 |
长度多算 1,后续反转区间越界 |
未处理 k>len |
无 k%len 取模 |
k 超出链表长度时,访问空指针(nullptr->next) |
| 反转区间错误 | 第三次反转起始为 len-k |
k>len 时区间为负数,程序崩溃 |
| 边界判断缺失 | 无 k==0 直接返回逻辑 |
旋转整倍数步时,仍执行反转,导致链表错乱 |
版本 2:修复核心问题(引用传递 + 边界处理)
cpp
class Solution {
public:
// 修复1:引用传递head,直接修改外部指针
void reverseList(ListNode* &head,int left,int right){
if (left == right) return; // 区间长度为1,无需反转
ListNode dummy(0,head);
ListNode* p0=&dummy;
for(int i=1;i<left;++i){
p0=p0->next;
}
ListNode* p1=nullptr;
ListNode* p2=p0->next;
// 修复2:反转循环次数(right-left+1次,确保完整反转)
for(int i=0;i<right-left+1;++i){
ListNode* p3=p2->next;
p2->next=p1;
p1=p2;
p2=p3;
}
p0->next->next=p2;
p0->next=p1;
head=dummy.next; // 引用传递生效,修改外部head
}
ListNode* rotateRight(ListNode* head, int k) {
// 修复3:边界预处理(空链表/单节点/无需旋转)
if (!head || !head->next || k == 0) return head;
// 修复4:正确计算链表长度
int len=0;
ListNode* l=head;
while(l){
len++;
l=l->next;
}
// 修复5:k取模,避免无效旋转
k %= len;
if (k == 0) return head; // 旋转整倍数,直接返回原链表
// 修复6:修正第三次反转区间(k+1到len)
reverseList(head,1,len); // 反转整个链表
reverseList(head,1,k); // 反转前k个节点
reverseList(head,k+1,len); // 反转剩余节点
return head;
}
};
关键修复点说明
- 引用传递
ListNode* &head:函数内对head的修改会直接同步到外部,解决「反转操作无效」的核心问题(替代「返回新头节点再赋值」的方式,更简洁)。 - 正确计算反转次数 :区间
[left, right]有right-left+1个节点,需要反转right-left+1次(逐个节点反转指针)。 - k 取模 + 边界判断 :
k %= len:将k缩减到[0, len-1],避免重复旋转;k==0直接返回:旋转整倍数步时,链表顺序不变,无需操作。
- 修正反转区间 :第三次反转起始位置为
k+1(剩余len-k个节点),而非len-k,确保区间合法。
四、最终最优实现(可直接提交)
cpp
class Solution {
public:
// 反转链表指定区间(引用传递head)
void reverseList(ListNode* &head, int left, int right) {
if (left == right) return; // 无需求反转
ListNode dummy(0, head);
ListNode* p0 = &dummy;
// 找到区间左边界的前驱节点
for (int i = 1; i < left; ++i) {
p0 = p0->next;
}
// 头插法反转区间内节点
ListNode* pre = nullptr;
ListNode* cur = p0->next;
for (int i = 0; i < right - left + 1; ++i) {
ListNode* nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
// 拼接反转后的区间
p0->next->next = cur; // 原区间首节点指向剩余节点
p0->next = pre; // 前驱节点指向反转后的区间头
head = dummy.next; // 更新外部head
}
ListNode* rotateRight(ListNode* head, int k) {
// 边界条件:空链表、单节点、无需旋转
if (!head || !head->next || k == 0) return head;
// 计算链表长度
int len = 0;
ListNode* curr = head;
while (curr) {
len++;
curr = curr->next;
}
// 缩减k的范围(避免无效旋转)
k %= len;
if (k == 0) return head;
// 三次反转实现右旋转
reverseList(head, 1, len);
reverseList(head, 1, k);
reverseList(head, k + 1, len);
return head;
}
};
五、关键知识点总结
- 链表操作核心原则 :
- 修改链表结构需操作「前驱节点」,而非节点本身;
- 值传递的指针无法修改外部变量,需用引用传递 或返回值更新;
- 所有访问
ptr->next的操作,必须先判断ptr非空。
- 三次反转法优势 :
- 时间复杂度 O (n):仅遍历链表常数次(计算长度 1 次 + 三次反转各 1 次);
- 空间复杂度 O (1):仅使用常数个指针,无额外空间开销;
- 逻辑可复用:反转区间的函数可直接用于其他链表反转问题(如反转指定区间、反转链表前 n 个节点)。
- 边界场景处理 :
- 空链表 / 单节点:直接返回原链表;
k=0或k%len=0:旋转整倍数步,链表不变;k>len:取模缩减范围,避免无效操作