Leetcode 234.回文链表


题目描述:

请判断一个链表是否为回文链表。

进阶:你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

示例 1:

1
2
输入: 1->2
输出: false

示例 2:

1
2
输入: 1->2->2->1
输出: true

链接:

https://leetcode-cn.com/problems/palindrome-linked-list


题目分析

  判断回文我们可以使用双指针的方法,从两边同时遍历判断是否相等,但是这道题的数据结构是单链表。第一个方法是将其拷贝到数组中再使用双指针,第二个方法是使用递归,反向获得链表的值,并与正向的相比较。但是这两种方法的空间复杂度都是 $O(n)$。如何达到 $O(1)$ 的空间复杂度呢?我们应该要使用迭代的方法。
  我们可以先使用快慢指针找到链表的中点,并用 Leetcode 206.反转链表 中的迭代法将后半部分翻转。翻转后两条链表应该是相同的(除了奇数结点的中间结点),这样我们就可以迭代判断是否符合回文特性了。然后我们再将后半部分翻转一遍(相当于复原)接回原链表。这样做是为了不破坏原链表的结构。
  注意代码中快慢指针寻找到的中点是前半部分的最后一个结点,并且如果结点数是奇数,最中间的结点会包含在前半部分中。这样做的好处是后半部分的开头可以直接通过前半部分结尾获得,并且复原后也是接到前半部分的结尾中。而由于奇数中间结点包含在前半部分中,我们判断回文的退出条件是后半部分遍历完成,这样这个结点无需进行判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class Solution {
// 快慢指针寻找前半部分结尾
ListNode* findFirstHalfEnd(ListNode* head){
ListNode *fast = head;
ListNode *slow = head;
while(fast->next != nullptr && fast->next->next != nullptr){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
// 反转链表
ListNode* reverseList(ListNode* head) {
if(head == nullptr) return nullptr;
ListNode *pre = head;
ListNode *p = head->next;
head->next = nullptr;
while(p != nullptr){
ListNode *next = p->next;
p->next = pre;
pre = p;
p = next;
}
return pre;
}
public:
bool isPalindrome(ListNode* head) {
// 寻找前半部分结尾
ListNode *firstHalfEnd = findFirstHalfEnd(head);
// 将后半部分反转并记录开头
ListNode *secondHalfBegin = reverseList(firstHalfEnd->next);
// 从两格部分开始遍历判断是否回文
bool result = true;
ListNode *p = head;
ListNode *q = secondHalfBegin;
while(q != nullptr){
if(p->val != q->val){
result = false;
break;
}
p = p->next;
q = q->next;
}
// 将后半部分复原并接回去
firstHalfEnd->next = reverseList(secondHalfBegin);
// 返回结果
return result;
}
};

  时间复杂度:$O(n)$,其中 $n$ 是链表的长度。我们对链表进行的寻找中点操作、反转操作、判断回文操作都是 $O(n)$ 的。
  空间复杂度:$O(1)$。我们所有的操作都是通过指针迭代进行的,需要的空间是常数级的。