如何识别回文链表呢?大家可以先思考一下,看看能不能做出力扣234. 回文链表这道题目。
识别以head为头结点的链表是否为回文链表分为三个步骤,分别是:
- 找出链表的中间结点mid。如果链表有偶数个结点,则找出偏右边的结点。如:1->2->3->2->1,中间结点是3;1->2->2->1,中间结点是右边的2。
- 逆置以mid为头结点的链表,假设逆置后的头结点为rmid。
- 遍历以head为头结点的链表和以mid为头结点的链表,判断是否为回文联表。
下面以链表1->2->2->1为例演示
初始状态:
找出中间结点mid。
逆置以mid为头结点的链表,新的头结点为rmid。
此时head视角:1->2->2->NULL;rmid视角:1->2->NULL。rmid视角和head视角的前半部分相同,则回文。
注:实际实现代码时,我直接用mid存储逆置后的链表,可以少定义一个变量。
下面来讨论如何实现这3个步骤。
step 1: 找出中间结点
我们可以定义2个指针,fast和slow。一开始两个指针都存储链表的头结点,接下来遍历链表,fast一次走2步,slow一次走1步,当fast走到链表尾部时,slow的位置就是中间结点。
struct ListNode* MiddleNode(struct ListNode* head)
{
struct ListNode* fast, * slow;
fast = slow = head;
// 快指针一次走两步,慢指针一次走一步
while (fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
step 2: 逆置链表
定义2个指针变量,n1和n2。一开始n1为NULL,n2存储链表的头结点。每次让n2结点指向n1结点,这样链表的方向就反过来了。注意:当n2结点的后继指针指向n1时,就找不到原链表的下一个结点了,所以要提前定义一个n3指针,记录原链表n2的后继指针。
struct ListNode* ReverseList(struct ListNode* head)
{
struct ListNode* n1 = NULL, * n2 = head;
while (n2)
{
struct ListNode* n3 = n2->next;
// 逆置结点关系
n2->next = n1;
// 迭代
n1 = n2;
n2 = n3;
}
return n1;
}
step 3: 结合前2个步骤,判断回文链表
判断原链表的前半部分和原链表后半部分逆置后的链表是否相同。
bool isPalindrome(struct ListNode* head) {
// 找出链表中间结点,如果是偶数个结点则找到中间偏右的结点
struct ListNode* mid = MiddleNode(head);
// 从中间结点开始,逆置后半段链表
mid = ReverseList(mid);
// 若逆置后的链表和前半部分相同,则回文
while (mid)
{
if (head->val != mid->val)
{
return false;
}
head = head->next;
mid = mid->next;
}
return true;
}
总结
- 链表中间结点思路:快慢指针法。
- 逆置链表思路:遍历链表,让每个结点指向前一个结点。
- 逆置链表的后半部分,若和前半部分相同,则为回文链表。
感谢大家的阅读!