题目描述
思路解析动画文字版
把值全存进数组、再首尾双指针往中间比,简单,但要 O(n) 额外空间。想做到 O(1) 空间,得在链表本身上动手。
转折:这题其实是 876 找中点 和 206 反转链表 的合体。先快慢找中点,把后半段原地反转,再从原头和反转后的尾两头往中间走逐个比值——全相等就是回文,只花 O(1) 空间。
① 快慢找中点 · 起步:第一步找中点:slow 走 1 步、fast 走 2 步,都从头出发。下面分开移动看得清楚。
① fast 第一跳 → 下标1:fast 这一步要走两格,先看第一跳:slow 还停在下标 0,fast 先跨一格到下标 1(第一个 2)。这只是 fast 两跳里的头一跳,还没走完。
① slow→2, fast 第二跳→第二个2:fast 第二跳:fast 再跨一格到下标 2(第二个 2),两跳走完;同时 slow 走 1 步到下标 1(值 2)。fast 把 slow 甩开,领先正好是 slow 走过距离的两倍。
① slow→第二个2, fast 越界停:第 2 轮:slow 走到下标 2(第二个 2),fast 想再跨两步就冲出链表(变 null),循环条件 fast 且 fast.next 不成立、停。slow 此刻把链表分成前半 [1,2] 和后半 [2,1],后半从 slow 开始。
② 反转后半段 · 起步:第二步反转后半段 [2,1]。从 slow 起设 cur=slow(下标 2)、prev=空。后半只有下标 2、3 两个节点,要把它们之间那根箭头掉头。
② 反转中 · 第一根箭头掉头:先存好 cur.next(下标 3),再把下标 2→3 那根箭头掉头(变 ←),让 3 指回 2。然后 prev 前进到 2、cur 前进到 3,继续。
② 后半段反转完成 [2,1]→[1,2]:cur 走过下标 3 后变空,反转结束。后半从 2→1 变成 1→2,新头是最后那个 1(下标 3),记作 prev——它就是双指针比较的右起点。
③ 左右双指针 · 第一对:第三步比较:left 从原头(下标 0,值 1)、right 从反转后半的新头(下标 3,值 1)。比第一对:1 等于 1,相等,两个一起往中间走。
③ 第二对 · left→2, right→2:left 沿正常箭头到下标 1(值 2),right 沿反转后的箭头到下标 2(值 2)。比第二对:2 等于 2,相等。right 再走就到空,比较即将结束。
负例实演 · [1,2,3,1] 某对不等立即 false:真换一条非回文 [1,2,3,1] 跑给你看:第一对 1=1 过,第二对 left=2、right=3,2 ≠ 3,这一刻就立即返回 false,不必比完——中途有一对不等,就不是回文。
③ 全部相等 · 是回文:right 走到头(后半比完),每一对都相等,返回 true。比较只走了半条链,不会越界。
难题往往是基础招式的拼装:找中点 + 反转 + 双指针。熟练的小套路,是解决大问题的积木——这正是「套路模板」的价值。
迁移阶梯:把这题练到能复述后,去链表专题里迁移:LC143 重排链表同样先找中点、反转后半,再前后交替穿插。不会的地方点右下角问问 AI 助教小欧。
边界三连:回文链表的边界先看空链表、单节点和奇数长度——奇数时正中间那个值不参与比较,这套写法天然处理掉了。
面试追问:回文链表的追问重点:O(1) 空间的动机、要不要恢复链表、奇偶长度如何天然处理。
这套「中点 + 反转后半」的骨架不止解回文:LC143 重排链表也是先找中点、再反转后半。先把它从本题里抽出来,下面就是可背模板。
参考代码
class Solution: def isPalindrome(self, head): slow = fast = head while fast and fast.next: # ① 快慢找中点 slow = slow.next fast = fast.next.next prev = None # ② 反转后半段 cur = slow while cur: nxt = cur.next cur.next = prev prev = cur cur = nxt left, right = head, prev # ③ 左右对撞比较 while right: if left.val != right.val: return False left = left.next right = right.next return True复杂度
- 时间复杂度:O(n),找中点扫半条、反转扫半条、比较扫半条,合起来仍是常数倍的 n
- 空间复杂度:O(1),只用 slow/fast/prev/cur/left/right 几个指针原地反转,不开数组
可套用的代码模板
记住骨架:快慢找中点 → 从中点起反转 → prev 是后半新头。拿到 prev 后接一段 while right 对撞比较就是回文判定;接一段前后交替穿插就是 LC143 重排链表。
slow = fast = headwhile fast and fast.next: # 快慢找中点 slow, fast = slow.next, fast.next.nextprev, cur = None, slow # 反转后半段while cur: nxt = cur.next cur.next = prev prev, cur = cur, nxt# 现在 prev 是后半段反转后的新头易错点
面试追问把动画讲成自己的话
追问为什么要做到 O(1) 空间,倒进数组不行吗?
追问反转后半段破坏了原链表,要不要恢复?
追问奇数长度怎么保证不出错?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
排序链表
LeetCode 148 · 中等 · 沿着 链表 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题