LeetCode 第 234 题「回文链表」
字数 1873 2025-10-25 22:55:12
好的,我们这次来详细讲解 LeetCode 第 234 题「回文链表」。
1. 题目描述
题目:给你一个单链表的头节点 head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false。
示例 1:
输入:head = [1,2,2,1]
输出:true
示例 2:
输入:head = [1,2]
输出:false
进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?
2. 题意理解
回文的意思是正着读和反着读一样。
对于链表来说,我们需要判断从前往后遍历和从后往前遍历得到的序列是否一致。
难点:
链表是单向的,只能从头往后遍历,不能直接反向遍历。
所以我们需要一种方法,能够比较“前半部分”和“反转后的后半部分”。
3. 思路分析
3.1 直观思路(但空间 O(n))
- 遍历链表,把值存入数组,然后用双指针判断数组是否回文。
- 时间复杂度 O(n),空间复杂度 O(n)(因为数组存储)。
- 这个方法简单,但空间不是 O(1),不满足进阶要求。
3.2 满足 O(1) 空间的思路
我们可以分三步:
- 找到链表的中间节点(快慢指针法)。
- 反转链表的后半部分。
- 比较前半部分和反转后的后半部分是否一致。
- (可选)恢复链表结构(题目一般不要求,但好习惯)。
4. 详细步骤
4.1 步骤 1:快慢指针找中点
- 快指针每次走两步,慢指针每次走一步。
- 当快指针到达末尾时,慢指针就在中间(或中间偏左,取决于链表长度奇偶)。
- 对于偶数长度
1→2→2→1,慢指针会停在第一个 2,然后我们取慢指针的下一个作为后半部分头。 - 对于奇数长度
1→2→3→2→1,慢指针停在 3,后半部分从 3 的下一个开始(即第二个 2),中间节点 3 在比较时忽略。
关键:我们想要把链表分成前后两半,后半部分反转后与前半部分比较。
4.2 步骤 2:反转后半部分链表
- 从中间节点的下一个节点开始反转(偶数情况)或中间节点下一个(奇数情况)。
- 反转链表的方法(迭代):
- 初始化
prev = null,curr = 后半部头。 - 每次记录
nextTemp = curr.next,curr.next = prev,prev = curr,curr = nextTemp。 - 直到
curr为 null,prev就是反转后的头。
- 初始化
4.3 步骤 3:比较前后两部分
- 用两个指针,一个从原链表头
head,一个从反转后的后半部头。 - 逐个比较节点值,如果全部相等则是回文。
- 注意奇数长度时,前半部分比后半部分多一个中间节点,比较时忽略中间节点(后半部分先遇到结尾)。
4.4 步骤 4:恢复链表(可选)
- 再次反转后半部分,接回原链表中间节点之后,保持链表原样。
5. 举例说明
例子 1:1 → 2 → 2 → 1
步骤 1:找中间节点
- 快慢指针初始都指向 1。
- 快一次走两步,慢一次一步:
- 慢:1,快:1
- 慢:2,快:2
- 慢:2,快:null(快指针走两步时,第一步到 2,第二步到 null,结束)
- 慢指针停在第一个 2(这是前半部分的尾)。
- 后半部分头 = 慢指针.next = 第二个 2。
步骤 2:反转后半部分
- 后半部分
2 → 1反转后为1 → 2,新头是 1。
步骤 3:比较
- 前半部分:
1 → 2 - 反转后的后半部分:
1 → 2 - 逐个比较:1=1,2=2,全部相等 → 是回文。
例子 2:1 → 2 → 3 → 2 → 1
步骤 1:找中间节点
- 快慢指针:
- 慢:1,快:1
- 慢:2,快:3
- 慢:3,快:1(快指针从 3 走两步:第一步到 2,第二步到 1)
- 慢指针停在 3(前半部分尾)。
- 后半部分头 = 慢指针.next = 第二个 2。
步骤 2:反转后半部分
- 后半部分
2 → 1反转后为1 → 2。
步骤 3:比较
- 前半部分:
1 → 2(3 是中间节点,不参与比较) - 反转后的后半部分:
1 → 2 - 逐个比较:1=1,2=2 → 是回文。
6. 代码实现(关键部分)
def isPalindrome(head):
if not head or not head.next:
return True
# 1. 快慢指针找中点
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# 2. 反转后半部分
prev = None
curr = slow # 从中间节点开始反转(包括中间节点)
while curr:
next_temp = curr.next
curr.next = prev
prev = curr
curr = next_temp
# 3. 比较前后部分
first, second = head, prev
while second: # 后半部分可能比前半部分短(奇数长度时)
if first.val != second.val:
return False
first = first.next
second = second.next
# 4. 可选:恢复链表(再次反转后半部分)
# 本题一般不要求,略过。
return True
7. 复杂度分析
- 时间复杂度:O(n),遍历几次链表,都是线性。
- 空间复杂度:O(1),只用了几个指针。
8. 总结
这道题结合了:
- 快慢指针找链表中点
- 反转链表
- 双指针比较
核心技巧在于将后半部分反转,从而可以在 O(1) 空间内实现回文判断。
这是一个非常经典的链表综合题,希望这个逐步讲解能帮助你彻底理解!