这是LeetCode上的 [027,回文链表],难度为 [简单]
题目
给定一个链表的 头节点 head ,请判断其是否为回文链表。
如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。
示例 1
输入: head = [1,2,3,3,2,1]
输出: true
示例 2:
输入: head = [1,2]
输出: false
题解(快慢指针,反转)
思路分析
回文链表有个特点就是对称性,即链表的第一个结点的值和最后一个结点的值相等,以此类推。因此我们可以把链表从中间划分为两个链表,然后把前一段链表或者后一段链表反转,最后同时遍历两链表,遍历过程中比较两结点的值,若不相等,则不是回文链表,否则是回文链表
需要注意的是,题意只让我们判断链表是否是回文链表,所以我们不能改变原有链表的结构,而反转链表需要尾结点的下一个结点为null,即我们不能把前一段链表进行反转(前一段链表反转需要断开链表,即尾结点需要指向null),只能把后一段链表反转(它的尾节点是指向null的),总的来说是逻辑上划分为两个链表,并不在物理结构上断开链表
步骤
第一步(把链表在逻辑结构上从中间划分为两个链表)
要想在逻辑结构上从中间划分为两个链表,需要求后一段链表的头结点,下面分为两种情况讨论
- 当链表长度为偶数时,链表关于中间链对称,需要从中间链中断开(逻辑结构,实际不断开),因此需要求前一段链表的尾结点,尾结点的下一个结点即为后一段链表的头结点
- 当链表长度为奇数时,链表关于中间结点对称,需要从中间结点的左右链中断开(中间结点是共用的,所以不用比较),因此需要求链表的中间结点,中间结点的下一个结点就是后一段链表的头结点
综上,我们可以使用快慢指针来求链表的中间结点,由于当链表为偶数时,需要求前一段链表的尾结点,因此逻辑(循环终止条件)会和求中间结点稍有不同
第二步(反转后一段链表)
第三步(同时遍历两链表,比较结点值)
第四步(再次反转后一段链表,恢复原有结构)
代码实现
public class Solution {
public boolean isPalindrome(ListNode head) {
/* 当链表为偶数时,返回前一段链表的尾结点,尾结点的下一个结点为后一段链表的头结点
* 当链表为奇数时,返回链表的中间结点,中间结点的下一个结点为后一段链表的头结点*/
ListNode firstHalfEndOrMiddleNode = EndOrMiddleNode(head);
// 返回后一段链表的头结点
ListNode secondHalfHead = firstHalfEndOrMiddleNode.next;
Boolean aBoolean = equals(head, reverseList(secondHalfHead));
// 恢复原有结构
reverseList(secondHalfHead);
return aBoolean;
}
public ListNode EndOrMiddleNode(ListNode head) {
ListNode slow = head;
ListNode fast = head;
/* 当链表长度为偶数时,终止条件为fast在链表倒数第二个结点时终止,即它的下下结点为null
* 此时slow在前一段链表的的尾结点
* 当链表长度为奇数时,终止条件为fast在链表的尾结点时终止,即它的下一个结点为null
* 此时slow在链表的中间结点*/
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
public ListNode reverseList(ListNode head) {
ListNode currNode = head;
ListNode preNode = null;
while (currNode != null) {
ListNode nextNode = currNode.next;
currNode.next = preNode;
preNode = currNode;
currNode = nextNode;
}
return preNode;
}
public Boolean equals(ListNode head1, ListNode head2) {
while (head2 != null) {
if (head1.val != head2.val) {
return false;
}
head1 = head1.next;
head2 = head2.next;
}
return true;
}
}
复杂度分析
假设l链表长度为n
时间复杂度:
最初需要遍历把链表划分为两半,时间复杂度为O(n),在反转后一段链表时,时间复杂度为O(n/2),最后同时遍历两链表时,时间复杂度为O(n/2),故总的时间复杂度为O(n) + 2O(n/2) = O(n)
空间复杂度:
只声明了几个固定的结点,故空间复杂度为O(1)