力扣刷题-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:取模缩减范围,避免无效操作
相关推荐
pearlthriving4 分钟前
STL容器及其底层
开发语言·c++·算法
念越6 分钟前
算法每日一题 Day04|快慢双指针法解决环形链表问题
数据结构·算法·链表
张人玉8 分钟前
VisionPro 药物检测工具 学习笔记
算法·c#·机器视觉·vsionpro
_深海凉_10 分钟前
LeetCode热题100-前 K 个高频元素
算法·leetcode·职场和发展
周末也要写八哥19 分钟前
深度剖析:动态规划的分类及实例
算法·动态规划
星马梦缘26 分钟前
离散数学——二元关系 作战记录
算法·离散数学
LDG_AGI30 分钟前
【搜索引擎】Elasticsearch(五):prefix前缀匹配方法大全(包含search_as_you_type等6种解法)
人工智能·深度学习·算法·elasticsearch·搜索引擎
她说彩礼65万33 分钟前
C语言 函数指针
c语言·开发语言·算法
王老师青少年编程33 分钟前
csp信奥赛C++高频考点专项训练之贪心算法 --【排序贪心】:纪念品分组
c++·算法·贪心·csp·信奥赛·排序贪心·纪念品分组
贾斯汀玛尔斯37 分钟前
每天学一个算法--贪心算法(Greedy Algorithm)
算法·贪心算法