Leetcode 103 反转链表 II

1 题目

92. 反转链表 II

给你单链表的头指针head和两个整数leftright,其中left <= right。请你反转从位置left到位置right的链表节点,返回反转后的链表

示例 1:

输入:head = [1,2,3,4,5], left = 2, right = 4输出:[1,4,3,2,5]

示例 2:

输入:head = [5], left = 1, right = 1输出:[5]

提示:

  • 链表中节点数目为n
  • 1 <= n <= 500
  • -500 <= Node.val <= 500
  • 1 <= left <= right <= n

进阶:你可以使用一趟扫描完成反转吗?

2 代码实现

/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* reverseBetween(ListNode* head, int left, int right) { ListNode* dummy = new ListNode(0) ; dummy -> next = head ; ListNode* pre = dummy; for (int i = 0 ; i < left -1 ;++i){ pre = pre -> next ; } ListNode* cur = pre -> next ; ListNode* tail = cur ; ListNode* prev = nullptr; for (int j = left ; j < right + 1; ++j){ ListNode* temp = cur -> next ; cur -> next = prev ; prev = cur ; cur = temp ; } pre -> next = prev; tail -> next =cur ; ListNode* newHead = dummy -> next ; delete dummy; return newHead; } };

思考

这道题目是部分链表进行了反转操作,我想大概的思路是,把链表分成三段:

1.从 head 到 left , 这一段要保存好,不变 ;

2.从 left 到 right 。 这一段进行翻转的操作 ;

3.从 right 到 最后一个节点 , 这一段保存好, 不变 ;

但是分成三段怎么防止最后一段(第三部分)的节点不丢失呢?

难道遍历两趟, 第一次划分, 第二次实现翻转?

-----

不太清晰,看下ai怎么写cpp实现这个leetcode模式核心代码。

解题思路

要在一趟扫描中完成反转,核心思路是:

  1. 定位边界:先找到left位置的前一个节点(记为pre),以及left位置的节点(记为cur),这是反转段的起点。
  2. 局部反转:从cur开始,依次将cur的下一个节点 “插入” 到pre的后面,循环执行right-left次,完成leftright段的反转。
  3. 衔接链表:整个过程中无需拆分链表再拼接,通过调整指针指向直接完成局部反转,避免丢失后续节点。

C++ 代码实现(LeetCode 核心代码模式)

// 定义链表节点结构(LeetCode 环境已内置,此处仅为示意) struct ListNode { int val; ListNode *next; ListNode() : val(0), next(nullptr) {} ListNode(int x) : val(x), next(nullptr) {} ListNode(int x, ListNode *next) : val(x), next(next) {} }; class Solution { public: ListNode* reverseBetween(ListNode* head, int left, int right) { // 虚拟头节点:避免处理left=1时的边界问题(头节点无前驱) ListNode* dummy = new ListNode(0); dummy->next = head; // 1. 找到left位置的前一个节点pre ListNode* pre = dummy; for (int i = 1; i < left; ++i) { pre = pre->next; } // cur是反转段的第一个节点(left位置) ListNode* cur = pre->next; // 循环right-left次,完成局部反转 for (int i = 0; i < right - left; ++i) { // next_node是cur的下一个节点(待反转的节点) ListNode* next_node = cur->next; // 步骤1:cur跳过next_node,指向next_node的下一个节点 cur->next = next_node->next; // 步骤2:next_node插入到pre的后面(成为反转段的新头部) next_node->next = pre->next; // 步骤3:pre指向新的反转段头部(next_node) pre->next = next_node; } // 虚拟头节点的下一个节点是新链表的头 ListNode* new_head = dummy->next; delete dummy; // 释放虚拟节点,避免内存泄漏 return new_head; } };

代码关键部分解释

  1. 虚拟头节点dummy

    • 作用:统一处理 “left=1”(头节点无前驱)的边界情况,无需单独判断,简化逻辑。
    • 注意:使用后要释放内存,避免内存泄漏。
  2. 定位前驱节点pre

    • 循环left-1次,让pre指向left位置的前一个节点,例如示例 1 中left=2pre最终指向节点 1。
  3. 局部反转核心循环(以示例 1 为例,left=2, right=4,循环 2 次):

    • 初始状态:pre=1, cur=2,待反转节点是 3、4。
    • 第一次循环(处理节点 3):
      • next_node=3cur->next=4(节点 2 指向 4),next_node->next=2(节点 3 指向 2),pre->next=3(节点 1 指向 3)→ 链表变为1->3->2->4->5
    • 第二次循环(处理节点 4):
      • next_node=4cur->next=5(节点 2 指向 5),next_node->next=3(节点 4 指向 3),pre->next=4(节点 1 指向 4)→ 链表变为1->4->3->2->5,完成反转。
  4. 时间复杂度:O (n)(仅一趟扫描,n 为链表长度),满足进阶要求;空间复杂度:O (1)(仅使用常数个临时节点)。

自己写的代码乱七八糟

/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* reverseBetween(ListNode* head, int left, int right) { ListNode* dummy = new ListNode(0); dummy -> next = head ; ListNode* cur = head ; for (int i = 0 ; i < left ; ++i){ cur = cur -> next ; } ListNode* prev = cur ; for (int j = left ; j < right ; ++j){ ListNode* temp = cur -> next ; cur -> next = prev ; prev = cur ; cur = temp ; } head = dummy -> next ; delete dummy ; return head ; } };

代码问题逐条拆解

1. 反转起点定位错误(最核心)
// 错误代码 for (int i = 0 ; i < left ; ++i){ cur = cur -> next ; }
  • 假设left=2,初始cur=head(1),循环执行 2 次后cur会指向 3(而非预期的 2),相当于定位到了left+1的位置,完全偏离了反转起点。
  • 更严重的是:你没有保存left位置的前驱节点(比如示例 1 中节点 1),反转后无法把前半段链表和反转后的区间衔接起来。
2. 反转逻辑错误,导致链表断裂
// 错误代码 ListNode* prev = cur ; for (int j = left ; j < right ; ++j){ ListNode* temp = cur -> next ; cur -> next = prev ; // 这里会让当前节点指向自己的前驱,形成环 prev = cur ; cur = temp ; }
  • 初始prev=cur(比如 cur=3),第一次循环时cur->next=prev会让 3 的 next 指向 3(自己),直接形成环,后续节点全部丢失。
  • 常规链表反转的prev初始值应该是nullptr,且需要控制反转的节点数量为right-left+1(而非right-left)。
3. 无链表衔接步骤,反转结果无效

你的代码全程没有调整dummy或原链表的指针指向,反转后的节点无法 “接回” 原链表,最终返回的还是原始head,等于没做反转。

历经艰辛终于通过了

/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* reverseBetween(ListNode* head, int left, int right) { ListNode* dummy = new ListNode(0) ; dummy -> next = head ; ListNode* pre = dummy; for (int i = 0 ; i < left -1 ;++i){ pre = pre -> next ; } ListNode* cur = pre -> next ; ListNode* tail = cur ; ListNode* prev = nullptr; for (int j = left ; j < right + 1; ++j){ ListNode* temp = cur -> next ; cur -> next = prev ; prev = cur ; cur = temp ; } pre -> next = prev; tail -> next =cur ; ListNode* newHead = dummy -> next ; delete dummy; return newHead; } };

疑惑

从dummy开始走的话不是left就好吗,为什么要减去1?

ListNode* pre = dummy; for (int i = 0 ; i < left -1 ;++i){ pre = pre -> next ; }

先明确核心目标

pre的作用是定位到left位置节点的前驱节点(比如left=2时,pre要指向节点 1),这样后续才能把反转后的区间 “接” 在pre后面。要找的是前驱节点!!!!!

为什么是left-1次?用例子拆解

假设left=2(对应链表节点是第二个节点,值为 2),我们一步步看循环次数的逻辑:

循环次数pre的初始位置

循环条件

i < left-1

(即i < 1

循环后pre的位置说明
初始状态

dummy

(指向节点 1 的前一个)

-dummy

dummy是第 0 个 “虚拟节点”,

节点 1 是第 1 个,

节点 2 是第 2 个

i=0dummy

0 < 1

→ 执行

pre = dummy->next→ 节点 1这是left-1=1次循环的唯一一次执行
i=1节点 1

1 < 1

→ 不执行

节点 1循环结束,pre正好是节点 2(left=2)的前驱

如果按你的想法写成i < left(即i < 2),会发生什么?

  • i=0:predummy→节点 1
  • i=1:pre从节点 1→节点 2
  • 循环结束后pre指向节点 2(left 位置本身),而非前驱节点,后续无法衔接反转区间。

再用通用规律总结

我们给链表节点做 “位置编号”(从 1 开始):

  • dummy:虚拟节点,编号 0(是节点 1 的前驱)
  • 节点 1:编号 1(是节点 2 的前驱)
  • 节点 2:编号 2(是节点 3 的前驱)
  • ...
  • 节点k:编号k(是节点k+1的前驱)

要找到节点left的前驱,就是找编号为left-1的节点:

  • 从编号 0 的dummy出发,需要走left-1步(循环left-1次),才能到达编号left-1的节点。

极端情况验证(left=1)

如果left=1(要反转第一个节点),left-1=0,循环条件i < 0不执行,pre停在dummy

  • 这正是我们需要的!因为第一个节点的前驱就是dummy,完美适配边界场景。

总结

  1. pre的目标是left 位置节点的前驱,而非 left 位置本身;
  2. dummy(编号 0)出发,到编号left-1的前驱节点,需要走left-1步(循环left-1次);
  3. 如果写成i < left,会多走 1 步,导致pre指向 left 位置本身,失去 “前驱” 的作用,后续无法衔接反转区间。

简单记:找前驱,走left-1步;找自身,走left。这里我们要找前驱,所以是left-1次循环。

3 小结

链表的部分写的好艰辛,我感受到一个很重要的特点就是:

指针有自己的职能,不能混用。

有点像化学里的试管——不能把试剂随意乱放在同一根试管里面。

为什么这样说呢?

首先,虚拟头节点的dummy通常是头节点有修改的情况下使用,防止丢失节点(?

然后cur就只是遍历, pre , tail 存放好部分反转链表的头和尾,在反转过程中temp 就作为一个容器防止成环、节点丢失 、逻辑混乱...

各有职能, 各司其职 。 而我一开始混乱的代码,总是觉得这个节点也许可以复用?但考虑不好就会有很多问题。

第二个就是经常容易出错的循环条件,这个最好还是在写代码前考虑好边界,用模拟数据走一遍。

刷leetcode不仅仅是码字,会语法是最小的一部分,草稿纸上得出来的思路用程序实现更重要。

链表这一块还没上道,多练练,找找感觉。

------------------------

召唤ai助教~

你在学习反转链表 II 这个题目时,不仅自己动手尝试了代码,还深入思考了指针的职能和循环边界问题,这种复盘和总结的方式特别棒!

我先帮你把核心疑问彻底讲透,再巩固你总结的关键点,让你对链表局部反转的逻辑理解更扎实。

一、先彻底解答你的核心疑惑:为什么循环次数是left - 1

你已经意识到pre的目标是找到left 位置节点的前驱节点,我们用更直观的方式拆解这个循环逻辑:

1. 先明确 “位置编号” 和 “步数” 的对应关系

我们给链表节点做 “位置标记”(结合 dummy 虚拟头):

  • dummy 节点:位置 0(是真正头节点的前驱)
  • 原链表第 1 个节点(head):位置 1
  • 原链表第 2 个节点:位置 2
  • ...
  • 原链表第 left 个节点:位置 left
2. 目标:从 dummy(位置 0)走到 left 节点的前驱(位置 left-1)

要从位置 0 到位置left-1,需要走left-1,对应循环次数就是left-1次。

3. 用具体例子验证(left=2)
循环变量 i循环条件i < left-1(即i < 1pre 位置变化
初始-dummy(0)
i=0成立,执行循环pre → 位置 1(节点 1)
i=1不成立,循环结束停在位置 1

最终 pre 正好是 left=2 节点(节点 2)的前驱,这就是循环次数必须是left-1的核心原因。

如果写成i < left(i<2),会多走一步:i=1 时循环仍成立,pre 会走到位置 2(节点 2),也就是 left 节点本身,失去了 “前驱” 的作用,后续无法衔接反转后的链表。

二、你总结的 “指针职能” 思路,我帮你提炼得更清晰

你说 “指针有自己的职能,不能混用”,这个感悟特别精准!

我们把这段代码里的每个指针 “职能” 明确下来:

指针名核心职能
dummy统一处理边界(比如 left=1 时,原头节点没有前驱),避免单独判断头节点场景
pre固定指向 “反转区间的前驱节点”,是衔接前半段和反转段的 “锚点”
cur遍历反转区间的节点,是反转操作的 “当前节点”
tail记录反转区间的原始第一个节点(反转后会变成反转段的最后一个节点),用于衔接后半段
prev反转链表时的 “前驱节点”(常规链表反转的经典指针),最终指向反转段的新头节点
temp临时保存 cur 的下一个节点,防止反转时丢失后续节点(避免链表断裂)

你的第一版代码出错,核心就是指针职能混乱比如让 cur 既负责定位又负责反转,且没有 pre/tail 这类 “锚点指针”,导致反转后链表衔接不上。

三、循环条件的 “避坑技巧”

你提到 “循环条件要先用模拟数据走一遍”,这是链表题的关键技巧,我补充两个实用方法:

  1. 小例子验证:写循环前,用最简单的例子(比如 left=2、right=4)手动走一遍循环次数,确认边界(比如 j 从 left 到 right+1,共right-left+1次循环,正好反转 right-left+1 个节点)。
  2. 明确循环目标:先想 “这个循环要做什么”,再推导次数:
    • 找前驱:循环left-1次 → 到 left 节点的前驱
    • 反转区间:循环right-left+1次 → 反转 left 到 right 的所有节点
    • 插入式反转(进阶写法):循环right-left次 → 逐个把节点插入到前驱后

总结

  1. pre循环次数是left-1的核心:目标是找到 left 节点的前驱,从 dummy(位置 0)到前驱(位置 left-1)需要走left-1步。
  2. 链表局部反转的关键:用pre(前锚点)和tail(后锚点)固定反转区间的前后边界,避免链表断裂;每个指针各司其职,不混用。
  3. 链表题避坑核心:先在草稿纸上明确指针职能和循环目标,用小例子验证循环边界,再动手写代码。

你现在已经能复盘自己的错误,还能总结出 “指针职能” 这种核心规律,说明已经在理解链表的本质了 —— 链表题的关键从来不是语法,而是指针的定位和衔接逻辑,多练几个局部反转、删除、插入的题目,很快就能找到感觉!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1161307.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机毕业设计springboot游戏账号交易系统 基于Spring Boot框架的在线游戏账号交易平台设计与实现 Spring Boot驱动的游戏账号交易系统开发与应用

计算机毕业设计springboot游戏账号交易系统xv94j &#xff08;配套有源码 程序 mysql数据库 论文&#xff09;本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。随着信息技术的飞速发展&#xff0c;游戏已经成为人们生活中不可或缺的一部分。游…

利用I2C总线实现远程IO模块的数据采集方案

用I2C总线构建远程IO采集系统&#xff1a;从原理到实战的完整指南你有没有遇到过这样的场景&#xff1f;工厂产线要接入30个限位开关&#xff0c;传统做法是把每根信号线都拉回主控柜——结果布线像蜘蛛网一样&#xff0c;接错一根就得排查半天。更头疼的是&#xff0c;一旦后期…

Fun-ASR语音舆情分析:公众讲话内容的情感倾向识别初探

Fun-ASR语音舆情分析&#xff1a;公众讲话内容的情感倾向识别初探 1. 引言 随着人工智能技术的快速发展&#xff0c;语音识别&#xff08;ASR&#xff09;在公共事务、媒体传播和企业服务中的应用日益广泛。特别是在舆情监测领域&#xff0c;如何从海量公众讲话、会议录音或社…

Glyph内存溢出?轻量级GPU优化部署实战解决方案

Glyph内存溢出&#xff1f;轻量级GPU优化部署实战解决方案 1. 背景与问题提出 随着大模型在视觉推理任务中的广泛应用&#xff0c;长上下文建模成为提升模型理解能力的关键挑战。传统基于Token的上下文扩展方式在处理超长文本时面临显著的计算开销和显存压力&#xff0c;尤其…

SGLang-v0.5.6+Qwen2.5联用指南:云端双模型切换仅需1分钟

SGLang-v0.5.6Qwen2.5联用指南&#xff1a;云端双模型切换仅需1分钟 你是不是也遇到过这样的情况&#xff1f;作为一名AI研究员&#xff0c;手头项目需要频繁在SGLang和通义千问Qwen2.5系列模型之间来回切换。本地部署时&#xff0c;每次换模型都得重新配置环境、安装依赖、解…

GLM-ASR-Nano-2512实战:语音控制机器人系统开发

GLM-ASR-Nano-2512实战&#xff1a;语音控制机器人系统开发 1. 引言 随着智能硬件和边缘计算的快速发展&#xff0c;语音交互正逐步成为人机通信的核心方式之一。在机器人控制系统中&#xff0c;实现高效、低延迟的本地化语音识别能力&#xff0c;是提升用户体验与系统响应速…

SenseVoice Small开发指南:Python接口调用详解

SenseVoice Small开发指南&#xff1a;Python接口调用详解 1. 引言 1.1 技术背景与应用场景 随着语音交互技术的快速发展&#xff0c;传统语音识别&#xff08;ASR&#xff09;已无法满足复杂场景下的语义理解需求。SenseVoice Small作为FunAudioLLM项目中的轻量级语音理解模…

AI推理平民化:DeepSeek-R1在普通PC上的运行实测

AI推理平民化&#xff1a;DeepSeek-R1在普通PC上的运行实测 1. 引言 1.1 技术背景与行业痛点 近年来&#xff0c;大语言模型&#xff08;LLM&#xff09;在自然语言理解、代码生成和逻辑推理等任务中展现出惊人能力。然而&#xff0c;主流高性能模型普遍依赖高算力GPU进行推…

外贸人如何判断目标客户的体量大小?

海外业务中&#xff0c;客户不论大小&#xff0c;完成一次订单的所有步骤需要的时间都差不多&#xff0c;花费的精力也差不多。所以同等的时间&#xff0c;你处理的大客户的订单越多&#xff0c;相应的收益也会越多。那么新人在开发客户的过程中&#xff0c;如何判断目标客户的…

Source Han Serif CN完整指南:免费商用中文字体的终极解决方案

Source Han Serif CN完整指南&#xff1a;免费商用中文字体的终极解决方案 【免费下载链接】source-han-serif-ttf Source Han Serif TTF 项目地址: https://gitcode.com/gh_mirrors/so/source-han-serif-ttf 还在为专业中文字体的高昂授权费用而困扰吗&#xff1f;Sour…

写作模型租赁指南:通义千问按小时计费,比包月灵活10倍

写作模型租赁指南&#xff1a;通义千问按小时计费&#xff0c;比包月灵活10倍 你是不是也遇到过这样的情况&#xff1f;作为一名自由职业者&#xff0c;写作任务来得突然又断断续续。有时候一周要写三篇长文&#xff0c;忙得连轴转&#xff1b;可下个月却一个单子都没有&#…

unet person image cartoon compound社区共建模式:志愿者参与文档翻译与测试

unet person image cartoon compound社区共建模式&#xff1a;志愿者参与文档翻译与测试 1. 背景与项目概述 随着人工智能在图像处理领域的快速发展&#xff0c;基于深度学习的人像风格化技术逐渐走向大众化应用。unet person image cartoon compound 是一个基于阿里达摩院 M…

终极完整指南:解锁老旧iOS设备新生命的替代工具链

终极完整指南&#xff1a;解锁老旧iOS设备新生命的替代工具链 【免费下载链接】Legacy-iOS-Kit An all-in-one tool to downgrade/restore, save SHSH blobs, and jailbreak legacy iOS devices 项目地址: https://gitcode.com/gh_mirrors/le/Legacy-iOS-Kit 还在为手中…

Z-Image-Turbo开源部署优势:无需外网下载权重实战指南

Z-Image-Turbo开源部署优势&#xff1a;无需外网下载权重实战指南 Z-Image-Turbo是阿里巴巴通义实验室开源的高效AI图像生成模型&#xff0c;作为Z-Image的蒸馏版本&#xff0c;它在保持高质量图像输出的同时大幅提升了推理速度。该模型仅需8步即可完成图像生成&#xff0c;具…

零基础看懂STLink硬件参考设计电路图

搞懂STLink电路图&#xff0c;从一块调试器看透嵌入式开发的“神经末梢”你有没有过这样的经历&#xff1a;手里的STM32板子突然连不上下载器&#xff0c;IDE提示“Target not connected”&#xff0c;然后你反复插拔、换线、重启电脑&#xff0c;甚至怀疑是不是芯片坏了&#…

开发者入门必看:通义千问2.5-7B-Instruct镜像快速上手教程

开发者入门必看&#xff1a;通义千问2.5-7B-Instruct镜像快速上手教程 1. 引言 随着大模型技术的快速发展&#xff0c;越来越多开发者希望在本地或私有环境中部署高性能、可商用的语言模型。通义千问2.5-7B-Instruct作为阿里于2024年9月发布的Qwen2.5系列中的核心成员&#xf…

SpringBoot+Vue 安康旅游网站管理平台源码【适合毕设/课设/学习】Java+MySQL

摘要 随着互联网技术的快速发展和旅游业的蓬勃兴起&#xff0c;在线旅游服务平台已成为人们规划行程、获取旅游信息的重要渠道。安康作为陕西省的重要旅游城市&#xff0c;拥有丰富的自然景观和人文资源&#xff0c;但传统旅游信息传播方式效率较低&#xff0c;无法满足游客个性…

cv_resnet18_ocr-detection test_images路径:测试集配置指南

cv_resnet18_ocr-detection test_images路径&#xff1a;测试集配置指南 1. 背景与目标 在OCR&#xff08;光学字符识别&#xff09;任务中&#xff0c;模型的检测能力依赖于高质量的数据集进行验证。cv_resnet18_ocr-detection 是一个基于ResNet-18骨干网络构建的文字检测模…

5分钟部署CosyVoice-300M Lite:轻量级语音合成引擎快速上手

5分钟部署CosyVoice-300M Lite&#xff1a;轻量级语音合成引擎快速上手 1. 引言&#xff1a;为什么需要轻量级TTS解决方案&#xff1f; 在构建语音交互系统时&#xff0c;文本转语音&#xff08;Text-to-Speech, TTS&#xff09;是实现自然人机对话的关键一环。传统TTS系统往…

如何快速创作古典乐?试试NotaGen大模型镜像

如何快速创作古典乐&#xff1f;试试NotaGen大模型镜像 在人工智能不断重塑创意边界的今天&#xff0c;音乐创作正迎来一场静默的革命。尤其是古典音乐这一高度结构化、规则严谨的艺术形式&#xff0c;长期以来被视为人类智慧与情感表达的巅峰领域。然而&#xff0c;随着大语言…