力扣hot100-234.回文链表-快慢指针与反转详解

发布时间:2026/7/31 20:17:29
力扣hot100-234.回文链表-快慢指针与反转详解 234. 回文链表快慢指针与反转详解题目链接234. 回文链表算法思路链表 / 快慢指针 / 反转后半段 / 双指针比较回文的含义是从左往右读与从右往左读完全一样。例如1 - 2 - 2 - 1从两端向中间看第 1 个节点和倒数第 1 个节点1 1 第 2 个节点和倒数第 2 个节点2 2因此它是回文链表。本题的核心流程是找中点 - 奇数长度时跳过中点 - 反转后半段 - 从两端向中间比较1. 为什么链表需要先反转后半段如果是数组可以直接通过下标从两端读取nums[left]nums[right]但单链表只能沿着next从前往后走1 - 2 - 2 - 1虽然可以从第一个节点走到最后一个节点却不能从最后一个节点走回前一个节点。也就是说链表没有办法像数组一样直接比较第一个节点 与 最后一个节点 第二个节点 与 倒数第二个节点解决方式是把后半段反转。例如原链表1 - 2 - 2 - 1 后半段2 - 1 反转后1 - 2现在可以同时从前向后读取两段左半段1 - 2 右半段1 - 2原本的“从两端比较”就转换成了“两个指针从前向后比较”。2. 如何找到中点快慢指针定义两个指针ListNodeslowhead;ListNodefasthead;移动规则slow 每轮走 1 步 fast 每轮走 2 步因此fast 到达末尾时slow 正好到达链表中间。代码while(fast!nullfast.next!null){slowslow.next;fastfast.next.next;}循环条件中的两个判断含义分别是fast ! nullfast 当前仍在链表内。 fast.next ! nullfast 还能再走两步不会访问空指针。3. 偶数长度时slow在哪里假设链表为1 - 2 - 2 - 1初始1 - 2 - 2 - 1 ^ slow、fast第 1 轮后1 - 2 - 2 - 1 ^ ^ slow fast第 2 轮后1 - 2 - 2 - 1 ^ slow fast null此时slow指向后半段的第一个节点1 - 2 | 2 - 1 ^ slow所以偶数长度时直接从slow开始反转后半段即可。4. 奇数长度时为什么要跳过中点假设链表为1 - 2 - 3 - 2 - 1快慢指针结束时1 - 2 - 3 - 2 - 1 ^ ^ slow fast此时slow 指向正中间节点 3 fast 不为 null且停在最后一个节点 1中间节点3没有需要比较的配对节点1 - 1 2 - 2 3 不需要配对因此应跳过它if(fast!null){slowslow.next;}跳过后1 - 2 - 3 | 2 - 1 ^ slow之后只反转2 - 1让它与左半段1 - 2对齐比较。为什么用fast ! null判断奇数长度偶数长度fast 每次刚好跨过两个节点最终走到 null。 奇数长度最后会剩一个节点无法再走两步fast 停在最后一个节点不是 null。5. 完整推演1 - 2 - 3 - 2 - 1第一步找到中点并跳过它快慢指针结束时1 - 2 - 3 - 2 - 1 ^ slow因为fast ! null这是奇数长度跳过中间节点31 - 2 - 3 - 2 - 1 ^ slow第二步反转后半段从slow开始的后半段是2 - 1 - null反转后得到1 - 2 - null它代表原链表从右到左读到的节点值。第三步逐个比较此时left 1 - 2 - 3 - ... right 1 - 2 - null比较过程left 的 1 right 的 1继续。 left 的 2 right 的 2继续。 right 到达 null比较完成。所以该链表是回文链表。6. Java 代码完整注释classSolution{publicbooleanisPalindrome(ListNodehead){// 空链表或只有一个节点时从正反两个方向读都相同。if(headnull||head.nextnull){returntrue;}// slow 每轮走一步fast 每轮走两步。// fast 到末尾时slow 会到达链表中间位置。ListNodeslowhead;ListNodefasthead;while(fast!nullfast.next!null){slowslow.next;fastfast.next.next;}// fast 不为 null说明节点数为奇数。// slow 此时指向正中间节点中点无需比较直接跳过。if(fast!null){slowslow.next;}// 反转后半段。// right 的遍历顺序等价于从原链表尾部向中间读取。ListNoderightreverse(slow);// left 从原链表头部开始读取。ListNodelefthead;// 后半段长度不会超过前半段。// 右半段全部匹配就说明整条链表是回文。while(right!null){if(left.val!right.val){returnfalse;}leftleft.next;rightright.next;}returntrue;}privateListNodereverse(ListNodehead){// pre 指向已经反转完成部分的头节点。ListNodeprenull;// cur 指向当前需要反转的节点。ListNodecurhead;while(cur!null){// 保存原来的下一个节点。// 因为下一步会覆盖 cur.next必须先保住后续链表。ListNodenextcur.next;// 让当前节点指向前一个节点完成当前节点的反转。cur.nextpre;// 移动指针继续反转原链表中的下一个节点。precur;curnext;}// pre 指向反转后链表的新头节点。returnpre;}}7. 为什么只比较到right nullright是反转后的后半段。对于偶数长度左半段长度 右半段长度对于奇数长度跳过中点后左侧可比较节点数 右半段长度所以无论奇偶只要右半段的每个节点都与左侧对应节点相同就已经完成了所有必要比较while(right!null)不需要等待left走到null奇数长度时left还会多经过中间节点以及后半段而它们都不应该重复参与比较。8. 这段代码会改变原链表吗会且改变发生在这一步ListNoderightreverse(slow);它会把原链表的后半段原地反转。LeetCode 这题通常只要求返回是否回文允许这种做法因为题目不会在方法返回后继续检查原链表的结构。如果在实际业务代码中后续还要使用原链表的原始顺序应当在比较结束后再次反转 right把后半段恢复。这不会改变时间复杂度仍然是O(n)。9. 复杂度假设链表有n个节点。时间复杂度找中点O(n) 反转后半段O(n) 比较两半O(n) 总计O(n)虽然进行了多个阶段但它们都是对链表进行常数次遍历因此总时间复杂度仍是线性的。额外空间复杂度O(1)只使用了有限个指针变量没有使用数组、栈或HashSet。一句话记忆快慢指针找中点奇数长度跳过中点反转后半段用左右指针从前向后逐个比较。