61. 旋转链表 ------ 三步成环法
这道题的核心在于:先连成环,再找断点,最后断开。
题目链接
题目简述
给定一个单链表和一个非负整数 k,将链表向右旋转 k 个位置。
例如:
- 输入:
1 → 2 → 3 → 4 → 5,k = 2 - 输出:
4 → 5 → 1 → 2 → 3
解题思路
采用的"成环 + 找断点"的策略:
- 先成环:把链表的尾部指向头部,变成一个环形链表。
- 再找断点 :从头部开始走
n - k - 1步(n是链表长度),找到新的尾部。 - 最后断开 :把新尾部的
next设为nullptr,新头部就是它的next。
代码详解(配合示意图)
cpp
ListNode* rotateRight(ListNode* head, int k) {
if(head == nullptr) return nullptr;
// 1. 找链表长度 n,并让 tem 指向最后一个节点
ListNode* tem = head;
int n = 1; // 从1开始,因为至少要算上头节点
while(tem->next != nullptr) {
n++;
tem = tem->next;
}
// 不能用下面的方法计算,这样最后扫描到的结点是空结点
// int n = 0;
// while(tem != nullptr)
// {
// n++;
// tem = tem -> next;
// }
// 2. 连成环:让最后一个节点指向头节点
tem->next = head;
// 3. 计算实际需要移动的步数(取模避免越界)
k = k % n;
int x = 1;
ListNode* p = head;
// 4. 走 n - k -1步,找到新链表的尾部
while(x <= n - k - 1) {
x++;
p = p->next;
}
// 5. 断开环,设置新的头尾
head = p->next; // 新的头是断点后的下一个节点
p->next = nullptr; // 断点处设为 nullptr
return head;
}
图解过程

初始状态:
head → 1 → 2 → 3 → 4 → 5 → nullptr
第一步:找长度和尾部
tem从head开始,走到5。n = 5。
第二步:成环
5 → head → 1 → 2 → 3 → 4 → 5 (环形)
第三步:找断点
k = 2,所以n - k - 1= 2。p从head出发,走 2 步:- 第1步:
p = 2 - 第2步:
p = 3
- 第1步:
- 此时
p指向3,它是新链表的尾部。
第四步:断开环
-
head = p->next = 4 -
p->next = nullptr -
结果:
4 → 5 → 1 → 2 → 3 → nullptr
关键点总结
-
为什么
tem->next != nullptr?因为我们想让
tem最终停在最后一个节点 ,而不是nullptr。如果写成tem != nullptr,tem会走到nullptr,那tem->next就炸了。 -
为什么
n从 1 开始?因为我们至少包含头节点。比如链表只有一个节点,
n=1,循环不会执行,tem还是头节点,没问题。 -
为什么是
n - k - 1?因为向右旋转
k步,相当于把前n - k个节点移到后面。所以新尾部就是 是第n - k个节点本身 !比如
n=5, k=2,新尾部是第 3 个节点(索引从0开始),新头部是第 4 个节点。 -
为什么
k = k % n?如果
k比n大,比如k=7, n=5,其实等价于k=2,因为转一圈等于没转。
复杂度分析
-
时间复杂度 :O(n)
一次遍历找长度,一次遍历找断点,总共 2n,所以是 O(n)。
-
空间复杂度 :O(1)
只用了几个指针变量,没有额外空间。
小结
这道题用"成环法"非常优雅,避免了复杂的数组拷贝或多次遍历。配合你的手绘图,逻辑一目了然:
成环 → 找点 → 断开,三步搞定旋转链表。
画图真的很有帮助,尤其是链表这种结构,一眼就能看出指针怎么走、断在哪里。