LeetCode中等难度题目「旋转链表」,这道题核心考察链表的遍历、长度计算和闭环处理,看似简单但容易踩坑,尤其是k值大于链表长度的情况,咱们一步步拆解思路、分析代码,帮大家吃透这道题。
一、题目描述(简化版)
给你一个单链表的头节点head,将链表每个节点向右移动k个位置,返回旋转后的链表头节点。
举个例子更直观:
-
输入:head = [1,2,3,4,5], k = 2 → 输出:[4,5,1,2,3]
-
输入:head = [0,1,2], k = 4 → 输出:[2,0,1](因为k=4,链表长度3,4%3=1,实际只需移动1位)
二、核心思路拆解
旋转链表的本质,是「将链表尾部的k个节点,移到链表头部」,但直接移动尾部节点会很繁琐(单链表无法直接获取前驱节点),所以我们换个更高效的思路,分4步走:
1. 边界处理(避免无效操作)
如果链表为空(head=null),或者k=0(不需要移动),直接返回原链表即可,这一步能减少后续无效计算。
2. 计算链表长度 + 闭环链表
遍历链表,一方面统计链表的总长度len,另一方面找到链表的尾节点p(遍历结束时p指向最后一个节点)。
此时将尾节点p的next指向头节点head,形成一个「闭环链表」------ 这样后续移动节点时,无需单独处理尾部和头部的衔接,简化操作。
3. 简化k值(关键一步)
当k大于链表长度len时,比如k=4、len=3,移动4次和移动1次的结果完全一致(因为移动len次会回到原链表)。
所以用k = k % len 简化k值,若简化后k=0,说明移动len的整数倍,直接返回原链表。
4. 找到新的头节点和尾节点,断开闭环
旋转后,新的尾节点是「原链表的第 len - k - 1 个节点」,新的头节点是新尾节点的next。
举个例子:head=[1,2,3,4,5],len=5,k=2 → len - k -1 = 2 → 新尾节点是第3个节点(值为3),新头节点是4;之后将新尾节点的next设为null,断开闭环,就得到旋转后的链表。
三、完整代码(TypeScript)
先定义链表节点类(题干已给出,直接复用),再实现旋转函数,每一步都加了注释,对应上面的思路:
typescript
// 链表节点类(题干自带)
class ListNode {
val: number
next: ListNode | null
constructor(val?: number, next?: ListNode | null) {
this.val = (val === undefined ? 0 : val)
this.next = (next === undefined ? null : next)
}
}
// 旋转链表核心函数
function rotateRight(head: ListNode | null, k: number): ListNode | null {
// 边界处理:空链表或无需移动
if (head === null || k === 0) {
return head
}
let len = 1 // 链表长度,初始为1(至少有头节点)
let p: ListNode | null = head // 用于遍历链表,找尾节点
// 1. 遍历链表,计算长度 + 找到尾节点
while (p?.next !== null) {
len++
p = p.next;
}
// 2. 简化k值,避免无效循环
k = k % len;
if (k === 0) { // 移动len的整数倍,链表不变
return head;
}
// 3. 找到新尾节点(原链表第 len - k - 1 个节点)
let newTail: ListNode | null = head;
for (let i = 0; i < len - k - 1; i++) {
newTail = newTail!.next; // 非空断言,因为前面已确认链表非空且k>0
}
// 4. 确定新头节点,断开闭环
const res = newTail!.next; // 新头节点是新尾节点的next
newTail!.next = null; // 断开闭环
p!.next = head; // 尾节点指向原头节点,衔接头部
return res; // 返回新头节点
};
四、易错点提醒(避坑关键)
-
易错点1:忽略k大于链表长度的情况 → 必须用k = k % len简化,否则会陷入无效遍历。
-
易错点2:忘记闭环链表 → 直接移动尾部节点会无法衔接头部,闭环后只需断开一次即可。
-
易错点3:新尾节点的位置计算错误 → 记住公式:新尾节点索引 = len - k - 1(索引从0开始)。
-
易错点4:空指针异常 → 遍历尾节点时,注意p?.next的判断(可选链操作),避免p为null时调用next。
五、复杂度分析
-
时间复杂度:O(n),其中n是链表长度。只需遍历链表2次(一次计算长度,一次找新尾节点),无嵌套循环。
-
空间复杂度:O(1),只使用了几个指针变量(p、newTail等),未使用额外空间,符合原地算法要求。
六、拓展思考
如果这道题要求「向左移动k个位置」,思路该如何调整?
提示:向左移动k个位置,等价于向右移动 len - k % len 个位置,只需修改k的简化逻辑即可,大家可以动手试试。
最后,这道题是单链表操作的经典题目,核心是理解「闭环简化衔接」和「k值简化」的思路,多敲几遍代码,就能熟练掌握啦。如果有疑问,欢迎在评论区交流。