力扣刷题-61.旋转链表

一、题目完整解读

题目描述

给你一个链表的头节点 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 个节点整体移到头部」,利用链表反转的特性,通过三次反转实现该逻辑:

  1. 反转整个链表 :把最后 k 个节点翻到链表头部(顺序反转);
  2. 反转前 k 个节点 :恢复这 k 个节点的原有顺序;
  3. 反转剩余 len-k 个节点:恢复剩余节点的原有顺序。

逻辑验证(以 [1,2,3,4,5], k=2 为例)

  1. 原链表:1→2→3→4→5
  2. 反转整个链表:5→4→3→2→1(最后 2 个节点 4,5 变前 2 个)
  3. 反转前 2 个节点:4→5→3→2→1(恢复 4,5 顺序)
  4. 反转剩余 3 个节点:4→5→1→2→3(恢复 1,2,3 顺序)
  5. 最终结果: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;
    }
};
核心坑点拆解
坑点 具体问题 后果
反转函数参数错误 reverseListhead 是值传递 函数内修改 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;
    }
};
关键修复点说明
  1. 引用传递 ListNode* &head :函数内对 head 的修改会直接同步到外部,解决「反转操作无效」的核心问题(替代「返回新头节点再赋值」的方式,更简洁)。
  2. 正确计算反转次数 :区间 [left, right]right-left+1 个节点,需要反转 right-left+1 次(逐个节点反转指针)。
  3. k 取模 + 边界判断
    • k %= len:将 k 缩减到 [0, len-1],避免重复旋转;
    • k==0 直接返回:旋转整倍数步时,链表顺序不变,无需操作。
  4. 修正反转区间 :第三次反转起始位置为 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;
    }
};

五、关键知识点总结

  1. 链表操作核心原则
    • 修改链表结构需操作「前驱节点」,而非节点本身;
    • 值传递的指针无法修改外部变量,需用引用传递返回值更新;
    • 所有访问 ptr->next 的操作,必须先判断 ptr 非空。
  2. 三次反转法优势
    • 时间复杂度 O (n):仅遍历链表常数次(计算长度 1 次 + 三次反转各 1 次);
    • 空间复杂度 O (1):仅使用常数个指针,无额外空间开销;
    • 逻辑可复用:反转区间的函数可直接用于其他链表反转问题(如反转指定区间、反转链表前 n 个节点)。
  3. 边界场景处理
    • 空链表 / 单节点:直接返回原链表;
    • k=0k%len=0:旋转整倍数步,链表不变;
    • k>len:取模缩减范围,避免无效操作
相关推荐
这波不该贪内存的2 小时前
【无标题】
算法·排序算法
靠沿2 小时前
【优选算法】专题二——滑动窗口
java·数据结构·算法
SoulruiA2 小时前
LeetCode-394. 字符串解码 递归思路
算法·leetcode·职场和发展
.小墨迹2 小时前
局部规划中的TEB,DWA,EGOplanner等算法在自动驾驶中应用?
开发语言·c++·人工智能·学习·算法·机器学习·自动驾驶
AI科技星2 小时前
张祥前统一场论 22 个核心公式及常数
服务器·人工智能·线性代数·算法·矩阵·概率论
苏婳6662 小时前
阿里巴巴校招软件笔试题经典
算法
阿猿收手吧!2 小时前
【数据结构】高效掌握并查集:核心原理与实战
数据结构·算法
励ℳ2 小时前
机器学习之线性回归算法:从原理到实践的全面解析
算法·机器学习·线性回归
_Twink1e2 小时前
[算法教学]一、前置知识
算法