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

【LeetHOT100】回文链表——Java多解法详解

一、题目描述

给你一个单链表的头节点head,请你判断该链表是否为回文链表。如果是,返回true;否则,返回false

示例 1:

输入:head = [1,2,2,1]
输出:true

示例 2:

输入:head = [1,2]
输出:false

提示:

  • 链表中节点数目在范围[1, 10⁵]

  • 0 <= Node.val <= 9

进阶:

你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

二、解题思路概览

判断回文的核心思想是:正序和逆序遍历的结果相同。但对于单链表而言,我们无法像数组那样从后往前遍历,因此需要借助一些技巧来实现“逆序访问”。

本文将介绍四种常见的解法,从易到难依次为:

解法时间复杂度空间复杂度特点
数组 + 双指针O(n)O(n)最直观,易于理解
栈辅助O(n)O(n)思路清晰,利用栈的LIFO特性
快慢指针 + 反转后半部分O(n)O(1)面试首选,满足进阶要求
递归O(n)O(n)代码简洁,但空间复杂度不优

三、解法一:数组 + 双指针(最直观)

3.1 思路

回文链表的特点是从前向后和从后向前读取的结果一致。我们可以:

  1. 遍历链表,将每个节点的值存入一个ArrayList中;

  2. 使用左右双指针,分别从数组的两端向中间移动,逐个比较值是否相等。

3.2 代码实现

java

class Solution { public boolean isPalindrome(ListNode head) { List<Integer> list = new ArrayList<>(); ListNode p = head; // 1. 将链表值存入数组 while (p != null) { list.add(p.val); p = p.next; } // 2. 双指针比较 int left = 0, right = list.size() - 1; while (left <= right) { if (!list.get(left).equals(list.get(right))) { return false; } left++; right--; } return true; } }

3.3 复杂度分析

  • 时间复杂度:O(n),遍历一次链表存入数组 + 遍历数组一半长度进行比较。

  • 空间复杂度:O(n),需要额外数组存储所有节点值。

四、解法二:栈辅助

4.1 思路

栈具有“先进后出”的特性,非常契合回文判断的需求:

  1. 遍历链表,将所有节点值依次压入栈中;

  2. 再次从头遍历链表,同时将栈顶元素弹出并与当前节点值比较;

  3. 如果所有值都相等,则是回文链表。

也可以优化为:只将前半部分节点值压入栈,然后与后半部分进行比较,减少空间使用。

4.2 代码实现

java

class Solution { public boolean isPalindrome(ListNode head) { if (head == null || head.next == null) { return true; } Deque<Integer> stack = new ArrayDeque<>(); ListNode p = head; // 1. 全部入栈 while (p != null) { stack.push(p.val); p = p.next; } // 2. 逐个出栈比较 p = head; while (p != null) { if (p.val != stack.pop()) { return false; } p = p.next; } return true; } }

4.3 复杂度分析

  • 时间复杂度:O(n),遍历两次链表。

  • 空间复杂度:O(n),栈需要存储所有节点值。

4.4 优化版:仅压入前半部分

java

class Solution { public boolean isPalindrome(ListNode head) { if (head == null || head.next == null) { return true; } // 快慢指针找中点 ListNode slow = head; ListNode fast = head; while (fast.next != null && fast.next.next != null) { slow = slow.next; fast = fast.next.next; } // 将后半部分入栈 Deque<Integer> stack = new ArrayDeque<>(); ListNode p = slow.next; while (p != null) { stack.push(p.val); p = p.next; } // 比较 p = head; while (!stack.isEmpty()) { if (p.val != stack.pop()) { return false; } p = p.next; } return true; } }

五、解法三:快慢指针 + 反转后半部分(最优解)

5.1 思路

这是面试中最常考察的解法,因为它满足O(n)时间复杂度 + O(1)空间复杂度的进阶要求。

核心思想分为三步:

  1. 找到链表中点:使用快慢指针(fast 每次走两步,slow 每次走一步),当 fast 走到末尾时,slow 恰好位于链表的中点位置;

  2. 反转后半部分:将中点之后的链表反转,使得后半部分的顺序变为逆序;

  3. 比较前后两部分:同时遍历前半部分和反转后的后半部分,逐个比较节点值。

5.2 图示理解

以链表1 → 2 → 2 → 1为例:

text

初始链表: 1 → 2 → 2 → 1 ↑ ↑ slow fast

经过快慢指针遍历后:

text

第一步(找中点): 1 → 2 → 2 → 1 ↑ ↑ slow fast 第二步(反转后半部分): 1 → 2 2 ← 1 (后半部分反转) ↑ ↑ ↑ head 慢指针 反转后的头 第三步(比较): 1 == 1,2 == 2,返回 true

5.3 代码实现

java

class Solution { public boolean isPalindrome(ListNode head) { if (head == null || head.next == null) { return true; } // 1. 快慢指针找中点 ListNode slow = head; ListNode fast = head; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } // 2. 反转后半部分链表 ListNode secondHalf = reverseList(slow); // 3. 比较前后两部分 ListNode firstHalf = head; while (secondHalf != null) { if (firstHalf.val != secondHalf.val) { return false; } firstHalf = firstHalf.next; secondHalf = secondHalf.next; } return true; } // 反转链表(迭代法) private ListNode reverseList(ListNode head) { ListNode prev = null; ListNode curr = head; while (curr != null) { ListNode nextTemp = curr.next; curr.next = prev; prev = curr; curr = nextTemp; } return prev; } }

5.4 一步到位版(找中点时同时反转前半部分)

有些面试官可能会要求不显式调用反转函数,而是将反转过程融入找中点的循环中。以下代码在快慢指针遍历的同时,将前半部分链表原地反转:

java

class Solution { public boolean isPalindrome(ListNode head) { ListNode slow = head; ListNode fast = head; ListNode pre = null; // 记录反转后的前半部分链表 // 快慢指针 + 同时反转前半部分 while (fast != null && fast.next != null) { ListNode next = slow.next; // 暂存下一个节点 fast = fast.next.next; // 快指针走两步 slow.next = pre; // 反转当前节点的指向 pre = slow; // pre 指向当前节点(新反转部分的头) slow = next; // 慢指针前进一步 } // 如果链表长度为奇数,跳过中间节点 if (fast != null) { slow = slow.next; } // 比较反转后的前半部分与后半部分 while (slow != null) { if (slow.val != pre.val) { return false; } slow = slow.next; pre = pre.next; } return true; } }

5.5 复杂度分析

  • 时间复杂度:O(n),只遍历了常数次链表。

  • 空间复杂度:O(1),只使用了几个指针变量,没有使用额外空间。

六、解法四:递归

6.1 思路

递归解法巧妙利用系统调用栈来实现从后向前遍历链表。具体思路:

  1. 定义一个递归函数,递归到链表的末尾;

  2. 在递归回溯的过程中,将当前节点与从头部出发的指针所指向的节点进行比较;

  3. 利用递归调用栈天然存储了链表的逆序访问顺序。

6.2 代码实现

java

class Solution { private ListNode frontPointer; public boolean isPalindrome(ListNode head) { frontPointer = head; return recursivelyCheck(head); } private boolean recursivelyCheck(ListNode currentNode) { if (currentNode != null) { // 递归到链表末尾 if (!recursivelyCheck(currentNode.next)) { return false; } // 回溯时比较 if (currentNode.val != frontPointer.val) { return false; } frontPointer = frontPointer.next; } return true; } }

6.3 复杂度分析

  • 时间复杂度:O(n),每个节点访问一次。

  • 空间复杂度:O(n),递归调用栈的深度等于链表长度,在最坏情况下会占用 O(n) 的栈空间。

七、解法对比与总结

解法时间复杂度空间复杂度是否修改原链表推荐场景
数组 + 双指针O(n)O(n)❌ 否快速实现,无空间限制
栈辅助O(n)O(n)❌ 否理解栈特性的练习
快慢指针 + 反转O(n)O(1)✅ 是面试首选,满足进阶要求
递归O(n)O(n)❌ 否展示递归思维

7.1 面试建议

在面试中,推荐使用解法三(快慢指针 + 反转后半部分),因为它满足进阶要求,同时也展示了你对链表操作和双指针技巧的掌握程度。

需要注意的点:

  • 处理边界条件:空链表和单节点链表直接返回true

  • 奇偶长度链表的处理:奇数长度时,中点节点不需要参与比较,需要跳过;

  • 是否需要恢复原链表:部分面试官可能会要求判断后恢复链表结构,此时需要将反转的后半部分再次反转回来。

7.2 关于修改原链表

解法三会修改原链表的结构(反转了后半部分)。如果题目要求不能修改原链表,可以选择解法一(数组)或解法四(递归)来解决。

八、相关链接

  • 题目链接:234. 回文链表 - 力扣(LeetCode)

  • 官方题解:回文链表官方题解

http://www.cnnetsun.cn/news/2049103.html

相关文章:

  • HGVE-2025-E009
  • 测试人员日常工作
  • 终极免费.brd文件查看方案:OpenBoardView电路板分析完全指南
  • 为机器人 Agent 设计 Harness 实时控制循环
  • 3步搞定Windows 10/11的PL2303老芯片驱动问题 [特殊字符]
  • 避开B题大坑!华中杯数学建模中‘文本转数据’的3个实用技巧与相似度计算实战
  • 【仅限首批200名开发者】获取2026 C安全编码合规检查器开源版:内置327条规则引擎、覆盖Linux/Windows/Zephyr三大平台,附内核模块级PoC验证源码
  • 别再折腾虚拟机了!用WSL2在Win11上5分钟搞定Ubuntu 22.04开发环境(附阿里云源配置)
  • 如何使用 GPT-Image-2 一键生成顶刊级科研图表
  • 链游革命2.0:源码开放与智能合约驱动的下一代游戏经济体
  • 说说MyBatis的工作原理吗?
  • OmenSuperHub:惠普游戏本终极性能解锁与风扇控制完整指南
  • 从零到一:在SpringBoot项目中集成sensitive-word实现敏感词实时过滤
  • AcWing 1097池塘计数题解:手把手教你用BFS/DFS搞定Flood Fill,附C++代码调试技巧
  • 视频智能分析:当多模态AI重新定义内容理解边界
  • 实时计算技术思考
  • C语言动态内存管理完全指南:从malloc到内存泄漏
  • STM32F103用CubeMX实现ADC欠采样:用800Hz采样率捕获1kHz正弦波的保姆级教程
  • 别再只盯着永恒之蓝了!手把手复现Samba CVE-2007-2447老漏洞(附Metasploit实战)
  • Real-ESRGAN-GUI:AI图像增强工具的终极指南
  • Clion配置Qt开发环境踩坑实录:MSVC 2017 vs MinGW,我的选择与避坑清单
  • 告别手动复制粘贴!用Python脚本批量抓取ENA数据库的Accession详细信息(附完整代码)
  • AI专著写作高效方案:AI工具一键生成20万字专著,格式规范易操作!
  • 揭秘AI写专著:AI专著生成工具大推荐,20万字专著轻松搞定!
  • vcpkg搭配CMake和VS Code:打造跨平台C++开发环境的完整配置指南
  • 新手程序员第一次接私活全流程指南:从注册平台到安全收款(避坑版)
  • 如何高效编辑赛博朋克2077存档:专业玩家的完整指南
  • 实战分享Flutter Web 开发:解决跨域(CORS)问题的终极指南
  • 从阿克曼角异响到安全转弯:冬季用车与直角转弯的完整指南
  • 从数据帧到精准定位:深度解析sensor_msgs/NavSatFix消息的实战应用