当前位置: 首页 > news >正文

逆序打印不可变链表技巧(力扣1265)

力扣第 1265 题 “逆序打印不可变链表” 要求在不修改链表结构、且只能使用给定接口getNext()getValue()的前提下,逆序打印一个不可变链表中所有节点的值 。

问题解构

  1. 核心约束

    • 不可变链表:链表节点本身不可修改,没有next指针域,只能通过ImmutableListNode接口提供的方法访问。
    • 给定接口
      • void printValue():打印当前节点的值。
      • ImmutableListNode* getNext():返回指向下一个节点的指针。如果当前节点是最后一个节点,则返回nullptr
    • 目标:逆序打印链表所有节点的值。
    • 限制:不能修改链表结构,不能使用额外的数据结构(如数组)存储所有节点值后再逆序输出(除非题目允许,但通常空间复杂度有要求)。
  2. 输入输出

    • 输入:一个ImmutableListNode类型的指针,指向链表的头节点。
    • 输出:逆序打印每个节点的值(通常通过调用节点的printValue()方法实现)。
  3. 关键挑战:由于链表是单向的且只能向前遍历,要“先访问后打印”以实现逆序,必须借助某种机制来“记住”之前访问过的节点或延迟打印操作。

方案推演与C++实现

解决此问题的核心思路是利用递归的“后进先出”(LIFO)特性,实现访问顺序与打印顺序的相反。以下是两种主要的C++实现方法。

方法一:递归(隐式栈)

递归在函数调用时会隐式地使用系统栈来保存每一层的状态(包括当前节点指针)。当递归到链表末尾(基线条件)开始返回时,再依次执行打印操作,自然实现了逆序。

算法思路

  1. 基线条件:如果当前节点为nullptr,直接返回。
  2. 递归步骤:先递归调用函数处理当前节点的下一个节点(head->getNext())。
  3. 打印操作:在递归调用返回后,再打印当前节点的值(head->printValue())。

C++ 实现

/** * // This is the ImmutableListNode's API interface. * // You should not implement it, or speculate about its implementation. * class ImmutableListNode { * public: * void printValue(); // print the value of the node. * ImmutableListNode* getNext(); // return the next node. * }; */ class Solution { public: void printLinkedListInReverse(ImmutableListNode* head) { // 基线条件:空节点则返回 if (head == nullptr) { return; } // 递归处理下一个节点 printLinkedListInReverse(head->getNext()); // 递归返回后,打印当前节点(实现了逆序) head->printValue(); } };
  • 时间复杂度:O(n),其中 n 为链表长度。每个节点被访问一次。
  • 空间复杂度:O(n),递归调用栈的深度等于链表长度。这是最简洁直观的解法 。

方法二:显式栈(迭代)

显式地使用一个栈(Stack)数据结构来存储节点指针,模拟递归的过程。

算法思路

  1. 遍历链表,将所有节点指针依次压入栈中。
  2. 遍历完成后,依次从栈顶弹出节点并打印其值。由于栈的LIFO特性,后进栈的节点(即原链表中靠后的节点)会先被弹出打印。

C++ 实现

class Solution { public: void printLinkedListInReverse(ImmutableListNode* head) { stack<ImmutableListNode*> nodeStack; // 第一步:遍历链表,所有节点指针入栈 ImmutableListNode* curr = head; while (curr != nullptr) { nodeStack.push(curr); curr = curr->getNext(); } // 第二步:从栈中弹出节点并逆序打印 while (!nodeStack.empty()) { ImmutableListNode* node = nodeStack.top(); nodeStack.pop(); node->printValue(); // 打印节点值 } } };
  • 时间复杂度:O(n),遍历链表一次,弹出栈一次。
  • 空间复杂度:O(n),栈需要存储所有节点指针。

方法对比与选择

特性递归解法显式栈解法
代码简洁性更简洁,逻辑清晰,代码量少。相对繁琐,需要显式管理栈数据结构。
空间复杂度O(n),使用系统调用栈。O(n),使用自己维护的栈。
适用场景链表长度不是特别大,且系统栈深度允许的情况下首选。当链表可能非常长,存在递归深度限制(栈溢出)风险时更安全。
控制与调试依赖系统栈,调试时调用栈信息直观。栈操作显式,对内存使用和控制更直接。

选择建议:在力扣等编程题环境中,只要链表长度在合理范围内(通常默认递归深度足够),递归解法是首选,因为它最贴合“逆序”的逻辑本质,且代码极其简洁 。只有在明确担心递归深度可能导致栈溢出时,才使用显式栈的迭代解法。

扩展:空间复杂度 O(1) 但时间复杂度 O(n²) 的解法

如果题目对空间复杂度有严格限制(要求O(1)),可以牺牲时间。思路是:对于每个位置 i,都从头遍历链表找到倒数第 i 个节点并打印。这需要双层循环。

C++ 实现 (O(1) 空间, O(n²) 时间)

class Solution { public: void printLinkedListInReverse(ImmutableListNode* head) { // 首先获取链表长度 int length = 0; ImmutableListNode* countPtr = head; while (countPtr != nullptr) { length++; countPtr = countPtr->getNext(); } // 从最后一个位置开始,依次寻找并打印每个节点 for (int i = length; i > 0; --i) { ImmutableListNode* curr = head; // 找到正数第 i 个节点(即倒数第 length-i+1 个节点,这里简化循环从后往前) // 更直接的方式:找倒数第 i 个节点,等效于走 length - i 步 for (int j = 1; j < i; ++j) { // 找到第 i 个节点,需要移动 i-1 次 curr = curr->getNext(); } curr->printValue(); } } };
  • 时间复杂度:O(n²)。获取长度 O(n),外层循环 O(n),内层寻找节点平均 O(n/2),故总体为 O(n²)。
  • 空间复杂度:O(1),只使用了几个固定变量。
  • 适用性:仅当内存极度受限,且可以接受平方级时间复杂度时使用。在实际面试或竞赛中,递归或显式栈解法更受认可。

总结与关联思考

  1. 核心思想:逆序打印不可变链表的核心是利用栈(无论是系统调用栈还是显式栈)的LIFO特性,将“先访问后处理”的顺序颠倒为“先递归/入栈,后返回/出栈时处理”。
  2. C++实现要点
    • 递归:基线条件判断空指针,递归调用getNext(),返回后调用printValue()
    • 显式栈:使用std::stack<ImmutableListNode*>,先遍历压栈,再循环弹栈打印。
  3. 关联题目模式:此题为链表逆序访问的经典问题,其变种或类似模式包括:
    • 逆序输出链表节点值(本题)。
    • 判断链表是否回文(结合快慢指针找到中点,然后逆序后半部分进行比较)。
    • 链表的后序遍历式操作(先处理子问题,再处理当前节点)。
  4. 工程考虑:在真实C++工程中,若链表极长,需警惕递归的栈溢出风险(通常默认栈空间为几MB)。此时应使用显式栈的迭代版本,或考虑其他非栈的线性算法(如先复制到可变数据结构,但可能违反“不可变”约束)。

因此,对于力扣1265题,在C++中最推荐使用递归解法,它完美契合题目要求,代码简洁,清晰展示了递归在解决此类“反向操作”问题上的优势 。


参考来源

  • LeetCode-Python-1265. 逆序打印不可变链表 (递归 + 栈)
http://www.jsqmd.com/news/789361/

相关文章:

  • 键盘连击问题终极解决方案:免费开源工具KeyboardChatterBlocker完整使用指南
  • C# Winform项目实战:给你的老旧桌面应用换上高清SVG皮肤(.NET Framework 4.5.1+)
  • TrustMem:为AI智能体构建可信记忆系统的架构与实践
  • 3分钟搞定:Windows系统苹果设备驱动一键安装终极方案
  • 龙芯杯团体赛:四人小队如何高效分工拿下SoC与Linux移植(含AXI接口与U-Boot实战)
  • AI项目规划工具:从提示工程到全栈架构的实践解析
  • Unity里用RenderTexture做擦玻璃效果,为什么你的笔刷总是断断续续?
  • 上海极证信息技术有限公司关于ISO 50001能源管理体系认证的解析 - 品牌企业推荐师(官方)
  • 如何彻底清除显卡驱动残留?DDU完全指南帮你解决90%的显示问题
  • 所有的框架源码,最怕的就是被debug
  • XUnity自动翻译器:3分钟快速安装的Unity游戏实时翻译终极解决方案
  • STM32F103模拟I2C避坑指南:为什么你的FreeRTOS任务里时序总出错?
  • ClawARR Suite:用Bash脚本与AI助手统一管理媒体服务器生态
  • 避坑指南:GNURadio连接RTL-SDR时‘USB打开错误-3’的几种原因及解决办法
  • 「幻觉」到底是什么机制:参数记忆、训练目标与缓解路径(不实操玄学)
  • Java地址解析终极指南:3步实现智能地址识别与标准化
  • Wireshark实战:从三次握手到四次挥手,图解TCP全生命周期数据包
  • 如何用智能工具重新定义硬件优化:一体化性能调校方案
  • 从罗克韦尔到贝加莱:一个工控工程师的软件安装避坑实录(附Automation Studio 4.7.2.98下载指南)
  • SpliceAI终极指南:深度学习剪接变异预测快速入门教程
  • 如何让老旧Mac免费升级最新macOS:OpenCore Legacy Patcher终极指南
  • 如何通过开源工具轻松获取网盘直链?终极网盘下载助手完整使用指南
  • 终极免费AMD Ryzen调试指南:5步掌握SMUDebugTool硬件调优核心技术
  • 为什么您的Windows系统驱动管理需要专业工具?Driver Store Explorer深度解析
  • 保姆级教程:在Ubuntu 20.04上从零部署NetData监控全家桶(含NVIDIA显卡监控与多服务器聚合)
  • 从.csv到3D点云:用Python解析Intel RealSense D435深度数据,告别官方查看器
  • 钉钉机器人签名计算时 URL 编码格式错误导致校验失败怎么办?
  • 告别迷茫!手把手教你用CodeWarrior 10.7为TWR-56F8200开发板创建第一个裸机工程
  • AI工具集开源实践:统一接口抽象与多模型集成设计
  • 天赐范式第37天:数值模拟到底算不算物理?——从KS和NS方程谈起