力扣(回文链表)

解析 LeetCode 234. 回文链表:快慢指针 + 后半段反转

一、题目分析

(一)问题定义

给定一个单链表的头节点 head,判断该链表是否为回文链表。例如 head = 1,2,2,1 返回 true。head = 1,2 返回 false。链表节点数范围是 1 到 10^5,值为整数,要求空间复杂度 O(1)。

(二)核心挑战

效率要求:必须 O(n) 时间 + O(1) 额外空间,排除转数组、递归栈、哈希表等方案。

结构适配:单链表无法倒序遍历,需通过反转局部子链实现双向比对,但反转会破坏原结构,必须精准控制断链与恢复时机。

二、算法思路:快慢指针找中点 + 反转后半段

(一)奇偶长度下慢指针终止位置差异

  • 奇数长度(如 5 个节点):慢指针停在正中节点(第 3 个),后半段从 next 开始(即第 4 个)。
  • 偶数长度(如 4 个节点):慢指针停在前半段末尾(第 2 个),后半段从 next 开始(即第 3 个)。
  • 统一处理:快指针走完时,慢指针刚好落在后半段起始位置的前一个,所以后半段头 = slow.next。

(二)反转后半段时断链与恢复的时机

  • 断链时机:找到后半段头后,立即将 slow.next 置为 null,切断前后两段,避免比对时误连。
  • 恢复时机:比对完成后,将已反转的后半段再次反转,再接回 slow.next,还原原始链表结构。
  • 关键细节:反转操作本身不依赖原链表后续,断链后可安全反转。恢复只需对反转后的子链再做一次反转即可。

三、代码实现与详细解析

java 复制代码
public class Solution
{
    public boolean isPalindrome(ListNode head)
    {
        if (head == null || head.next == null)
        {
            return true;
        }
        // 快慢指针初始化:slow 和 fast 都从 head 出发
        ListNode slow = head;
        ListNode fast = head;
        // 移动快慢指针:fast 走两步,slow 走一步,直到 fast 到达末尾
        // 奇数长度时 fast 停在 tail,偶数时停在 tail.next(null)
        while (fast.next != null && fast.next.next != null)
        {
            slow = slow.next;
            fast = fast.next.next;
        }
        // 此时 slow 停在前半段末尾,后半段头是 slow.next
        ListNode secondHalfHead = slow.next;
        // 断链:切断前半段和后半段的连接,避免比对干扰
        slow.next = null;
        // 反转后半段链表,得到新头节点 reversedSecondHalf
        ListNode reversedSecondHalf = reverseList(secondHalfHead);
        // 初始化两个指针:分别从前半段头和反转后的后半段头开始比对
        ListNode p1 = head;
        ListNode p2 = reversedSecondHalf;
        boolean isPalin = true;
        // 逐节点比对:p1 和 p2 同时前进,值不同则不是回文
        while (p1 != null && p2 != null)
        {
            if (p1.val != p2.val)
            {
                isPalin = false;
                break;
            }
            p1 = p1.next;
            p2 = p2.next;
        }
        // 恢复链表:将已反转的后半段再次反转,接回 slow.next
        slow.next = reverseList(reversedSecondHalf);
        return isPalin;
    }
    // 辅助函数:反转链表,返回新头节点
    private ListNode reverseList(ListNode head)
    {
        ListNode prev = null;
        ListNode curr = head;
        // 遍历原链表,逐个将 curr 指向 prev,实现反转
        while (curr != null)
        {
            ListNode nextTemp = curr.next; // 保存下一个节点,防止断链丢失
            curr.next = prev;               // 当前节点指向前面节点
            prev = curr;                    // prev 前移一位
            curr = nextTemp;                // curr 前移一位
        }
        return prev; // prev 是新头节点
    }
}

(一)代码流程拆解

  1. 初始化: 处理空链表或单节点情况,直接返回 true。设置 slow 和 fast 均指向 head。
  2. 快慢指针移动: fast 每次走两步,slow 每次走一步,循环条件是 fast.next 和 fast.next.next 都非空,确保 fast 不越界。
  3. 定位后半段头: 循环结束时 slow 停在前半段末尾,secondHalfHead = slow.next 即为后半段起点。
  4. 断链操作: 将 slow.next 置 null,彻底分离前后两段。
  5. 反转后半段: 调用 reverseList 得到反转后的后半段头节点。
  6. 双指针比对: p1 从前半段头出发,p2 从反转后半段头出发,同步前进比对值,任一不等即返回 false。
  7. 恢复链表: 对已反转的后半段再调用 reverseList,结果接回 slow.next,还原原始结构。
  8. 返回结果: 比对全程相等则返回 true,否则 false。

(二)关键逻辑解析

断链时机:快慢指针停稳后立刻执行 slow.next = null,否则比对时 p2 可能顺着原链表继续走到前半段,造成错误匹配。例如 1,2,3,2,1 中若不断链,p2 从 3 开始反转后为 1,2,3,但未断链时仍连着 2→1,比对会混乱。

恢复必要性:题目虽未要求保持原链表,但实际工程中修改输入结构需谨慎,且部分 OJ 测试用例会复用链表,恢复可避免副作用。

奇偶统一性:无论长度奇偶,slow 停点都是后半段起始的前一个,所以 secondHalfHead = slow.next 恒成立,无需分支判断。

四、复杂度分析

(一)时间复杂度

O(n),其中 n 是链表长度。快慢指针遍历约 n/2 次,反转后半段约 n/2 次,比对最多 n/2 次,恢复反转又 n/2 次,总和为 O(n)。

(二)空间复杂度

O(1),只使用了 slow、fast、p1、p2、prev、curr 等常数个指针变量,无递归栈、无额外容器。

相关推荐
土司大王1 小时前
LeetCode hot100——35.搜索插入位置:Java 二分模板、左闭右开区间与插入点分析
java·算法·leetcode
微三云生态系统架构师-彭丹1 小时前
远方好物S2B2C系统架构:一级分销与保证金托管的合规技术实现
人工智能·算法
土司大王1 小时前
LeetCode hot100——34.在排序数组中查找元素的第一个和最后一个位置:Java 二分模板、边界分析
java·算法·leetcode
罗西的思考3 小时前
机器人模型(WM / WAM / VLA)综合分析与对比:从「看」到「想」再到「做」
人工智能·算法·机器学习
小哈里4 小时前
【知识】从学科到实践:自然・工程・社科・人文|算法・研发・产品・品牌设计
程序人生·算法·产品·设计·工程
小π军4 小时前
最大重叠区间数量
数据结构·算法
镜像视界(浙江)科技有限公司5 小时前
《视频孪生之上:二维展示终结,三维空间计算重构城市逻辑》——跨摄像连续表达 × 三角测量厘米级定位 × 动态轨迹建模,构建新一代城市空间
大数据·人工智能·算法·矩阵·音视频·空间计算
a187927218315 小时前
【算法】双指针与滑动窗口(二):滑动窗口——吃进、判定、吐出
算法·leetcode·双指针·滑动窗口·原理·模板·算法讲解
铭哥的编程日记5 小时前
从一道 LeetCode Hard 到吃透一类题:加权区间调度「排序 + 二分 + DP」
算法·leetcode·职场和发展
Navigator_Z6 小时前
LeetCode //C - 1248. Count Number of Nice Subarrays
c语言·算法·leetcode