1. 旋转链表这道题到底在考什么
1.1 从题目表面到本质需求
看到“61. 旋转链表”这个题号,很多人第一反应是“又一个链表移动题”。但说实话,这道题在LeetCode上的难度标注虽然是中等,实际区分度却很高。会的人三分钟写完,不会的人改了半天还在死循环里打转。原因在于,它表面考的是“旋转”,实际上考的是三件事:链表遍历与计数、取模运算的边界处理、以及指针断开的时机把控。
题目本身不复杂:给定一个链表的头节点 head,将链表每个节点向右移动 k 个位置。比如 1->2->3->4->5,k=2,得到 4->5->1->2->3。这种操作在真实业务里其实很常见,最典型的就是轮换调度场景——一组任务节点按顺序执行,每轮从不同的起点开始,本质上就是对任务链表做旋转。还有日志分片、缓存队列的滚动淘汰,底层抽象出来都是这个模型。所以这道题不是纯粹的应试玩具,它在工程里的映射非常直接。
我觉得学习这道题的正确姿势,不是死背题解,而是理清楚它背后的几个关键决策点:第一,旋转的本质是什么;第二,k 非常大会发生什么;第三,怎么用尽量少的指针操作完成这件事。把这三点想明白了,代码就是水到渠成的事。
1.2 面试官最想看到的全局观
这道题在面试中出现频率很高,通常不是单独出现,而是作为链表专题的第一题或第二题。面试官通过它考察的维度很明确:
- 基本素养:能不能准确计算出链表长度,能不能快速判断 k 是否需要取模。
- 指针操控能力:链表题的核心就是若干指针的移动和断链、接链。旋转题需要恰到好处地在正确的位置切开,多了少了都会出问题。
- 代码健壮性:空链表、单节点链表、k=0、k 是长度的整数倍,这些边界你是否都能处理利索,在面试官眼里比主流程代码更重要。
- 复杂度意识:能不能说出 O(n) 时间、O(1) 空间,并且解释清楚为什么不能更优。
换句话说,这道题是一块很好的“试金石”。如果你的旋转链表代码能一次性通过,并且能清晰讲出每一步指针操作的动机,那你的链表功底基本过关了。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 闭环法:先转成一个圈,再切开
2.1 模型直觉:项链和断口
我第一次解这道题的时候,用的是比较常规的“找新头节点然后拆链”思路。后来看到题解区有人用“闭环法”,当时就觉得这个思路太妙了——它把“旋转”这件事还原成它本来的样子:一个环在转,你只需要选择在哪里剪开。
你可以这么想象:一条项链,上面串着几颗珠子,对应链表的节点。所谓“向右旋转 k 步”,听起来像是在项链上移动珠子,操作起来很别扭。但如果先把项链的搭扣扣上,变成一个完整的环,那么旋转就变成了“在这个环上另找一个点,把环重新剪开”——剪开的位置决定了新的头节点是谁,新的尾节点是谁。
这就是闭环法最核心的直觉:旋转不改变节点之间的相对顺序,只改变头尾的位置。 与其去想“怎么把后面的节点搬到前面来”,不如先把尾部和头部接起来,让整个链表变成一个环,然后在指定位置断开。断开的那条边,就是旋转之后新尾节点和新头节点之间的边。
这个思路能省掉很多不必要的指针操作,代码也短得多。更重要的是,它好理解、好记忆,不容易出错。
2.2 闭环法的操作步骤拆解
具体来说,闭环法只需要四步:
- 遍历链表,计算长度 n,同时让尾节点指向头节点,形成环。
- 遍历时用一个指针 cur,从 head 开始,走到最后一个节点(
cur.next == None)为止。 - 此时 cur 就是尾节点,让
cur.next = head,链表变成环。
- 遍历时用一个指针 cur,从 head 开始,走到最后一个节点(
- 对 k 取模:
k = k % n。- 因为旋转一整圈等于没转,所以 k 只需要对 n 取余。
- 找到新的尾节点位置。
- 向右旋转 k 位,等价于把最后 k 个节点移到最前面。那么新链表的尾节点,就是原链表的第 n-k 个节点。
- 此时再用一个指针从 head 出发,走 n-k-1 步,或者复用 cur(它现在在尾节点),继续走 n-k 步,就能到达新尾节点。
- 断开环,返回新头节点。
- 记
new_head = cur.next,然后设置cur.next = None,断开环。
- 记
这里有一个容易搞混的点:到底走 n-k 步还是 n-k-1 步?我建议用一种不容易出错的方式——**复用已经停在尾节点的 cur,然后
