LeetCode Hot 100 —— 234.回文链表
234. 回文链表
题目
给你一个单链表的头节点 head,请你判断该链表是否为回文链表。如果是,返回 true;否则,返回 false。
示例 1:
输入: head = [1,2,2,1]
输出: true
示例 2:
输入: head = [1,2]
输出: false
提示
- 链表中节点数目在范围
[1, 10^5]内 0 <= Node.val <= 9
进阶
你能否用 $O(n)$ 时间复杂度和 $O(1)$ 空间复杂度解决此题?
一、复制 + 反转比较
思路
该方法的核心思想是:一边反转链表,一边将原链表的值复制到新链表中,最后比较反转后的链表与值副本链表是否相同。
具体过程:用 prev 和 curr 两个指针遍历并反转原链表。每访问一个节点,就将它的值存入另一个新链表 num 中。遍历结束后,prev 指向反转后的链表头,num 链保存了原始顺序的值。最后逐个节点比较即可。
这个方法会修改原链表的结构(反转操作),不过本题通常允许修改链表。
代码实现
bool isPalindrome(struct ListNode* head) {
struct ListNode *num_head = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode *num = (struct ListNode*)malloc(sizeof(struct ListNode));
num_head->next = num;
struct ListNode *prev = NULL;
struct ListNode *curr = head;
while (curr != NULL) {
struct ListNode *next = curr->next;
curr->next = prev;
prev = curr;
num->val = curr->val;
num->next = (struct ListNode*)malloc(sizeof(struct ListNode));
num = num->next;
curr = next;
}
num = num_head->next;
while (prev != NULL) {
if (prev->val == num->val) {
num = num->next;
prev = prev->next;
} else
return 0;
}
return 1;
}
- 时间复杂度: $O(n)$,需要遍历链表两次(一次反转并复制,一次比较)。
- 空间复杂度: $O(n)$,需要额外复制一份链表的所有节点值。
二、快慢指针 + 只反转后半部分
思路
这是满足进阶要求($O(n)$ 时间 + $O(1)$ 空间)的一种解法。核心思想是:只反转链表的后半部分,再与前半部分比较。
为什么这样可以判断回文?回文的定义是正着读和反着读一样。如果把链表后半部分反转过来,前半部分从头到尾的顺序正好对应后半部分从尾到头的顺序,逐节点比较即可。
具体分为以下几步:
- 找中间节点:使用快慢指针。
fast每次走两步,slow每次走一步。当fast走到末尾时,slow刚好指向链表中间位置(偶数长度时指向中间靠左的节点,奇数长度时指向正中间节点)。 - 反转后半部分:从
slow->next开始,反转后半段链表,得到新头节点rev。 - 断开连接:将
slow->next置为NULL,让前半部分独立出来(非必须,但防止遍历时互相干扰)。 - 逐一比较:指针
p1从头开始遍历前半部分,p2从rev开始遍历反转后的后半部分,逐个比较节点值。若全部相等,则是回文链表。
代码实现
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode *prev = NULL, *curr = head;
while (curr) {
struct ListNode *next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
bool isPalindrome(struct ListNode* head) {
if (!head || !head->next) return true;
// 快慢指针,slow 最终指向中间前一个(偶数)或中间(奇数)
struct ListNode *slow = head, *fast = head;
while (fast->next && fast->next->next) {
slow = slow->next;
fast = fast->next->next;
}
// 反转后半部分(slow 之后的部分)
struct ListNode *rev = reverseList(slow->next);
// 断开前半部分(非必须,但防止干扰)
slow->next = NULL;
// 比较前半部分(head 开始)和反转后的后半部分
struct ListNode *p1 = head, *p2 = rev;
while (p1 && p2) {
if (p1->val != p2->val) return false;
p1 = p1->next;
p2 = p2->next;
}
// 奇数长度时 p2 先空,偶数同时空,均合法
return true;
}
为什么 while (fast->next && fast->next->next)?
- 偶数长度
[1,2,3,4]:循环结束后slow指向2(中间靠左),slow->next指向3(后半部分起点)。 - 奇数长度
[1,2,3,2,1]:循环结束后slow指向3(正中间),slow->next指向2(后半部分起点)。 - 这种写法保证
slow始终指向后半部分的前一个节点,方便切出后半部分。 - 到结束时,只要有其中的一个值为
NULL就说明了已经到底了。
- 时间复杂度: $O(n)$,只需遍历链表常数次(快慢指针一次,反转后半部分一次,比较一次)。
- 空间复杂度: $O(1)$,只使用了若干指针变量,没有额外新建与节点数相关的空间,满足进阶的要求。

浙公网安备 33010602011771号