【码道初阶】Leetcode234进阶版回文链表:牛客一道链表Hard,链表的回文结构——如何用 O(1) 空间“折叠”链表?

【LeetCode/牛客】链表的回文结构:如何用 O(1) 空间"折叠"链表?

在判断字符串或数组是否回文时,我们通常使用双指针从两头向中间逼近。但对于单向链表,我们无法轻易地"从后往前"遍历。

最直观的方法是将链表元素存入数组或栈中,但这就需要 O(N)O(N)O(N) 的额外空间。如果题目卡死了 空间复杂度 O(1),我们该怎么办?

今天我们要拆解的,就是一种通过修改链表结构来实现回文判断的高阶解法。

1. 核心解题思路:找到中点,折叠比较

既然无法从后往前遍历,那我们不妨把链表的后半段反转过来,让它指向中间。这样,我们就可以分别从"头"和"尾"出发,向中间走,一一比对。

整个过程分为三步(这也是代码中的三个主要模块):

  1. 快慢指针找中点:确定链表的一半在哪里。
  2. 反转后半部分:将后半段链表的指针方向反转,使其指向中点。
  3. 双指针汇合判断:一个从头走,一个从尾走,检查数值是否相等。

2. 代码深度解析

让我们结合代码,一步步看懂这个过程。

第一步:快慢指针找中点

java 复制代码
// 1、快慢双指针遍历找到中间元素
ListNode fast = A;
ListNode slow = A;
while(fast != null && fast.next != null)
{
    fast = fast.next.next;
    slow = slow.next;
}
  • 原理fast 一次走两步,slow 一次走一步。当 fast 走到链表末尾时,slow 刚好走到链表的中点。
  • 细节
    • 如果是奇数个节点(如 1->2->3->2->1),slow 会停在正中间(3)。
    • 如果是偶数个节点(如 1->2->2->1),slow 会停在前半段的最后一个(第一个 2)。

第二步:反转后半部分(高能预警)

这是代码中最精妙也最容易晕的地方:

java 复制代码
// 2、反转链表
ListNode cur = slow.next;
while(cur != null)
{
    ListNode curN = cur.next;
    cur.next = slow; // 【关键】反转指向,指向前一个节点
    slow = cur;      // slow 移动到当前节点,作为新的"头"
    cur = curN;      // cur 继续处理下一个
}

此时链表变成了什么样?

假设链表是 1 -> 2 -> 3 -> 2 -> 1

经过这一步,原本向右指的箭头,在后半段变成了向左指:

结构变成了:1 -> 2 -> 3 <- 2 <- 1

  • A 依然指向头部 1
  • 注意 :此时变量 slow 已经跑到了链表的最末尾 (也就是反转后的"新头"),即右边的 1

第三步:双向逼近判断

java 复制代码
// 3、判断回文
while(A != slow)
{
    // 值不相等,直接判定不是回文
    if(A.val != slow.val)
    {
        return false;
    }
    
    // 【易错点高亮】偶数节点的特殊处理
    if(A.next == slow)
    {
        return true;
    }
    
    slow = slow.next; // 从后往前走(因为指针反转了)
    A = A.next;       // 从前往后走
}
return true;

3. ⚠️ 易错点与逻辑陷阱(重点)

这段代码虽然短,但有两个非常容易写错的逻辑,必须重点标注!

易错点 1:反转逻辑中的节点连接

很多人在反转链表时习惯把 slow.next 置空,或者切断中间的联系。但在这个解法中,我们保留了中间的连接

  • cur.next = slow:这一句把后半段的节点反向指回了 slow
  • 对于奇数链表,中间节点(如 3)成为了连接点,左边指向它,右边也指向它。

易错点 2:偶数链表的判断陷阱 (A.next == slow)

这是整段代码最容易被忽略的细节!

假设链表是偶数长度:1 -> 2 -> 2 -> 1

  1. 反转后结构变成:1 -> 2(前) <-> 2(后) <- 1
  2. A 在左边 1slow 在右边 1。值相等,继续走。
  3. A 走到 2(前)slow 走到 2(后)
  4. 此时注意Aslow 指向的是两个不同的节点对象,虽然它们的值都是 2,但内存地址不同,所以 while(A != slow) 循环继续。
  5. 在下一轮循环前,如果没有 if(A.next == slow) 这个判断:
    • A 会继续往后走到 2(后)
    • slow 会继续往前走到 2(前)
    • 两人"擦肩而过",导致循环多跑或者逻辑混乱。

必须提前判断:

A 的下一个节点就是 slow 时(即 Aslow 相邻),说明对于偶数链表来说,已经比对完毕了,直接返回 true

4. 总结

这道题考核了对链表指针的绝对掌控力。

  • 常规解法:栈/数组,空间 O(N)。
  • 大神解法:快慢指针找中点 + 局部反转,空间 O(1)。

代码通过原地修改链表指向 ,巧妙地把一个单向链表变成了"两头往中间走"的结构,尤其是对偶数链表 A.next == slow 的判断,是点睛之笔。

虽然这种做法破坏了原链表的结构(如果需要还原,还需要再反转一次),但在算法题中,这是追求极致空间效率的标准答案。


这个算法在面试中非常高频,理解了它,链表类的题目基本就通了一半。

相关推荐
CoderYanger2 小时前
C.滑动窗口-求子数组个数-越长越合法——2962. 统计最大元素出现至少 K 次的子数组
java·数据结构·算法·leetcode·职场和发展
小满、2 小时前
Redis:高级数据结构与进阶特性(Bitmaps、HyperLogLog、GEO、Pub/Sub、Stream、Lua、Module)
java·数据结构·数据库·redis·redis 高级特性
月明长歌3 小时前
【码道初阶】Leetcode面试题02.04:分割链表[中等难度]
java·数据结构·算法·leetcode·链表
如竟没有火炬3 小时前
快乐数——哈希表
数据结构·python·算法·leetcode·散列表
蒙奇D索大3 小时前
【数据结构】考研408 | B树探秘:从查找操作到树高性能分析
数据结构·笔记·b树·考研·改行学it
聆风吟º3 小时前
【顺序表习题|图解|双指针】移除元素 + 删除有序数组中的重复项
c语言·数据结构·c++·经验分享·算法
炽烈小老头3 小时前
【每天学习一点算法 2025/12/10】反转链表
学习·算法·链表
学困昇3 小时前
Linux 进程概念与内存管理详解(含冯诺依曼体系结构、环境变量、调度算法)
linux·c语言·开发语言·网络·数据结构·c++
爱学习的小仙女!3 小时前
数据结构基本概念
数据结构