LeetCode 61. 旋转链表:题解+思路拆解

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值简化」的思路,多敲几遍代码,就能熟练掌握啦。如果有疑问,欢迎在评论区交流。

相关推荐
linux_cfan23 分钟前
17 · 引擎适配器全景:HLS/DASH/Vimeo/Mux/Cast
前端·javascript·音视频
漂流瓶jz27 分钟前
UVA-1442 洞穴 题解答案代码 算法竞赛入门经典第二版
c++·算法·题解·aoapc·算法竞赛入门经典·uva
圣保罗的大教堂28 分钟前
leetcode 835. 图像重叠 中等
leetcode
计算机魔术师1 小时前
烧掉2780亿美元还不够?OpenAI的资本豪赌让人头皮发麻
前端
叠层归一研究院2 小时前
基于极限自指的叠层归一宇宙结构理论——无元外部封闭系统的内生区分模型
人工智能·经验分享·算法·agi
IT_陈寒2 小时前
Java线程池用错参数,我的服务居然悄悄崩溃了
前端·人工智能·后端
卡布鲁2 小时前
React useMemo 与 useCallback 完全指南:原理、场景与避坑
前端·react.js
碳基修炼2 小时前
排查记:本地 devServer 是 http,浏览器却把 302 重定向升级成了 https
前端·http
步行cgn2 小时前
Spring p 命名空间注入详解
java·前端·spring
何何____2 小时前
Vue 生命周期详解
前端·javascript