LeetCode 234. 回文链表
本节目标
反转链表后半段进行对称比较,并在返回答案前恢复原链表。
题意与边界
判断单链表从前到后的值序列是否为回文。目标是 O(n) 时间和 O(1) 辅助空间;比较过程中可以临时改动指针,但返回前恢复原链表,使调用者观察不到结构副作用。
中点、反转与恢复
快慢指针让 slow 停在前半段末尾,再原地反转 slow.next 开始的后半段。从头部和反转后的后半段同步比较,只需以后半段结束为界。无论比较成功还是失败,都把保存的后半段头再次反转并接回 slow.next。
正确性依据
后半段反转后,其遍历顺序正好对应原序列从尾到中间的顺序。前后两个指针逐项相等,当且仅当原序列关于中心对称。奇数长度的中间节点不参与成对比较,不影响结论;第二次反转是第一次反转的逆操作,因此完整恢复所有连接。
代码实现
- C++
- Python
C++17
class Solution {
private:
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* cur = head;
while (cur != nullptr) {
ListNode* nxt = cur->next;
cur->next = prev;
prev = cur;
cur = nxt;
}
return prev;
}
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return true;
}
ListNode* slow = head;
ListNode* fast = head;
while (fast->next != nullptr && fast->next->next != nullptr) {
slow = slow->next;
fast = fast->next->next;
}
ListNode* second = reverseList(slow->next);
ListNode* right = second;
ListNode* left = head;
bool matches = true;
while (right != nullptr) {
if (left->val != right->val) {
matches = false;
break;
}
left = left->next;
right = right->next;
}
slow->next = reverseList(second);
return matches;
}
};
Python 3
from typing import Optional
class Solution:
@staticmethod
def _reverse(head: Optional[ListNode]) -> Optional[ListNode]:
prev = None
cur = head
while cur is not None:
nxt = cur.next
cur.next = prev
prev = cur
cur = nxt
return prev
def isPalindrome(self, head: Optional[ListNode]) -> bool:
if head is None or head.next is None:
return True
slow = head
fast = head
while fast.next is not None and fast.next.next is not None:
slow = slow.next
fast = fast.next.next
second = self._reverse(slow.next)
right = second
left = head
matches = True
while right is not None:
if left.val != right.val:
matches = False
break
left = left.next
right = right.next
slow.next = self._reverse(second)
return matches
复杂度分析
- 时间复杂度:
O(n),找中点、两次反转和比较都是线性扫描。 - 空间复杂度:
O(1),只维护有限个指针。
易错点
- 比较失败后直接返回,导致后半段没有恢复。
- 奇数长度时把中间节点也当成需要配对的元素。
- 把所有值复制到数组中,虽简单但不满足进阶空间目标。
模式迁移
“先局部变换、完成判断、再撤销变换”的结构适用于需要暂时重排输入、但又必须保持调用者可观察状态不变的问题。实现时应把恢复步骤放在所有提前退出路径之后统一执行。