方法一:存数组双指针
cpp
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> vals;
while (head != nullptr) {
vals.emplace_back(head->val);
head = head->next;
}
for (int i = 0, j = (int)vals.size() - 1; i < j; ++i, --j) {
if (vals[i] != vals[j]) {
return false;
}
}
return true;
}
};
方法二:反转后半部分 再比较
cpp
/**
* 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) {
if(head==nullptr){
return true;
}
ListNode *firsthalfend = middlenode(head);//找中点
ListNode *secondnode = reverselist(firsthalfend->next); //反转链表 找末尾点
ListNode *p1 = head;
ListNode *p2 = secondnode;
while(p2!=nullptr){
if(p1->val !=p2->val){
return false;
}
p1 = p1->next;
p2 = p2->next;
}
return true;
}
ListNode *middlenode(ListNode *head){
ListNode *slow = head;
ListNode *fast = head;
while(fast->next!=nullptr && fast->next->next!=nullptr){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
ListNode *reverselist(ListNode *head){
ListNode *pre = nullptr;
ListNode *cur = head;
while(cur!=nullptr){
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
这段代码的核心思路可以精简成一句话:
先用快慢指针找到前半部分的末尾,再反转后半部分,最后让前后两部分同时向后遍历比较。
具体过程是:middlenode(head) 用快慢指针找中点,slow 每次走一步,fast 每次走两步,因此当 fast 到链表尾部时,slow 位于前半部分末尾。然后通过
reverselist(firsthalfend->next)
把后半部分链表反转。比如:
1 -> 2 -> 2 -> 1
变成比较:
1 -> 2
1 -> 2
之后:
p1 = head;
p2 = secondnode;
两个指针同时向后走,只要出现:
p1->val != p2->val
就说明不是回文,返回 false;如果 p2 全部遍历结束都相等,就返回 true。
你可以把整题记成:
找中点
↓
反转后半部分
↓
前后两部分逐个比较
↓
全部相等 → true
出现不同 → false
易错点
1. 找中点时要判断 fast,不是 slow。
正确:
while (fast->next != nullptr &&
fast->next->next != nullptr)
因为 fast 一次走两步,必须提前保证它后面有节点,否则容易出现:
nullptr->next
导致空指针错误。
2. 反转链表循环条件要写 cur != nullptr。
while(cur != nullptr)
因为真正不断移动的是:
cur = next;
如果误写成:
while(head != nullptr)
由于 head 没有变化,很可能导致死循环。
3. 反转链表一定要先保存下一个节点。
ListNode* next = cur->next;
cur->next = pre;
不能先反转再找 cur->next,否则原来的后续链表会丢失。
所以顺序一定是:
保存 next
↓
反转箭头
↓
pre 前进
↓
cur 前进
4. 后半部分从 firsthalfend->next 开始。
正确:
reverselist(firsthalfend->next)
不是:
reverselist(firsthalfend)
否则会把中间节点也一起反转,容易把链表结构弄乱。
5. 比较循环看 p2 是否结束。
while(p2 != nullptr)
因为我们只需要比较后半部分的长度。对于奇数长度链表,中间节点不需要参与比较。
例如:
1 -> 2 -> 3 -> 2 -> 1
只需要比较:
1 -> 2
1 -> 2
中间的 3 不影响回文判断。
6. bool 函数最后别忘了 return true;。
return true;
否则所有节点都比较成功之后,函数没有返回值,会出现:
non-void function does not return a value
最后可以把这题压缩成一个记忆口诀:
快慢找中点,后半做反转;双指针比较,不同就返回。
时间复杂度是 O(n) ,额外空间复杂度是 O(1)。