234. 回文链表 LeetCode 热题 HOT 100
目录
- 过程解析
- 1. 问题背景
- 2. 算法核心思路
- 3. 快慢指针寻找中点
- 快慢指针原理
- 示例 1(奇数长度)
- 示例 2(偶数长度)
- 4. 翻转后半部分链表
- 翻转示例(以偶数链表 1 → 2 → 2 → 1 为例)
- 5. 比较两部分节点值
- 6. 可选:恢复链表
- 7. 复杂度分析
- 代码
- C++
- C语言
过程解析
1. 问题背景
本题要求判断一个单链表是否为回文链表(Palindrome Linked List),即链表从前往后读和从后往前读的值序列完全相同。
例如:
- 输入:
1 → 2 → 2 → 1
,输出:true
- 输入:
1 → 2 → 3 → 2 → 1
,输出:true
- 输入:
1 → 2
,输出:false
2. 算法核心思路
该算法采用 快慢指针 + 翻转后半部分 + 双指针比较 的方式:
- 使用快慢指针(fast, slow)找到链表中点。
- 从中点开始翻转链表的后半部分。
- 从头部与后半部分同时向后比较节点值,若完全相同则为回文。
3. 快慢指针寻找中点
快慢指针原理
- slow 每次走 1 步
- fast 每次走 2 步
当循环结束时:
- 若链表长度为 奇数,
slow
指向中间节点; - 若链表长度为 偶数,
slow
指向后半部分的第一个节点。
示例 1(奇数长度)
链表:1 → 2 → 3 → 2 → 1
步骤 | slow | fast | 说明 |
---|---|---|---|
初始 | 1 | 1 | 两者都在头节点 |
第1次循环 | 2 | 3 | slow走1步,fast走2步 |
第2次循环 | 3 | 1(最后一个) | slow走到中间节点 |
循环结束 | slow = 3 | fast = nullptr | 找到中点(3) |
此时 slow
正好指向中间节点(3),后半部分为 2 → 1
。
示例 2(偶数长度)
链表:1 → 2 → 2 → 1
步骤 | slow | fast | 说明 |
---|---|---|---|
初始 | 1 | 1 | 两者都在头节点 |
第1次循环 | 2 | 2 | slow走1步,fast走2步 |
第2次循环 | 2(第二个) | nullptr | 循环结束 |
结果 | slow = 第二个2 | fast = nullptr | slow 指向后半段起点 |
此时后半部分为 2 → 1
,前半部分为 1 → 2
。
4. 翻转后半部分链表
从 slow
开始,将后半部分链表原地翻转。
核心思想是:逐步反转 next
指针的方向,让链表“反着连”。
- 暂存下一个节点
- 当前节点反向指向前一个节点
- prev 前进到当前节点
- curr 前进到下一个节点
翻转完成后,prev
将指向翻转后的新表头(即原链表的尾部)。
翻转示例(以偶数链表 1 → 2 → 2 → 1 为例)
初始:
1 → 2 → 2 → 1↑slow
翻转步骤:
步骤 | 操作 | 翻转后局部结构 | prev | curr |
---|---|---|---|---|
1 | 翻转节点 2(第二个) | 2 → nullptr | 2 | 1 |
2 | 翻转节点 1 | 1 → 2 → nullptr | 1 | nullptr |
最终结果:
前半段:1 → 2
后半段(翻转后):1 → 2
现在链表后半部分被独立翻转,prev
指向翻转后的新头节点(原尾节点)。
5. 比较两部分节点值
设置两个指针:p1
,p2
逐节点比较:
- 若任意一对节点值不同,则不是回文;
- 若所有对应节点都相同,则为回文。
注意:当链表长度为奇数时,前半段包括中间节点,所以应该以后半段长度为基准遍历
6. 可选:恢复链表
原理同 4
若希望不破坏原链表结构,可再次翻转后半部分恢复原状。
7. 复杂度分析
- 时间复杂度: O(n)
(找中点 O(n/2) + 翻转 O(n/2) + 比较 O(n/2)) - 空间复杂度: O(1)
(只使用常数级指针)
代码
C++
/*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *next) : val(x), next(next) {}* };*/
class Solution {
public:bool isPalindrome(ListNode* head) {// 当节点数量为 0 或 1 时,直接返回 trueif(!head || !head->next) return true;// 使用快慢指针找到链表中点// slow 每次走一步,fast 每次走两步// 当 fast 到达链表末尾时,slow 位于中点ListNode *slow = head, *fast = head;while(fast && fast->next){slow = slow->next; // 慢指针走一步fast = fast->next->next; // 快指针走两步}// 循环结束时,slow 指向后半段的起点(若链表长度为奇数,则是中间节点)// 翻转后半段链表// 示例:原链表 1→2→3→2→1// slow 指向第二个“3”处,翻转后半段得到:1→2→3←2←1ListNode *curr = slow, *prev = nullptr;while(curr){ListNode *nextNode = curr->next; // 暂存下一个节点curr->next = prev; // 反转指针方向prev = curr; // prev 向后移动curr = nextNode; // curr 向后移动}// 翻转结束后,prev 指向翻转后的新表头(原链表的尾部)bool isPalin = true; // 用于记录是否为回文// 比较前半段与翻转后的后半段// p1 从头开始,p2 从后半段(翻转后的起点)开始ListNode *p1 = head;ListNode *p2 = prev;while(p2){ // 只需比较后半部分长度即可if(p1->val != p2->val){ // 若任意节点值不同,则不是回文isPalin = false;break;}p1 = p1->next;p2 = p2->next;}// 可选步骤:恢复链表原状// 若不希望破坏原链表结构,可再次反转后半部分恢复/*curr = prev;prev = nullptr;while(curr){ListNode * nextNode = curr->next;curr->next = prev;prev = curr;curr = nextNode;}*/// 返回结果return isPalin;}
};
C语言
/*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/
bool isPalindrome(struct ListNode* head) {if(!head||!head->next) return true;struct ListNode *fast = head,*slow = head;while(fast&&fast->next){slow = slow->next;fast = fast->next->next;}struct ListNode *prev = NULL,*curr = slow;while(curr){struct ListNode *nextNode = curr->next;curr->next = prev;prev = curr;curr = nextNode;}bool isPalind = true;struct ListNode *p1 = head,*p2 = prev;while(p2){if(p1->val!=p2->val){isPalind = false;break;}p1 = p1->next;p2 = p2->next;}/*curr = prev;prev = NULL;while(curr){struct ListNode *nextNode = curr->next;curr->next = prev;prev = curr;curr = nextNode;}*/return isPalind;
}