回文字符串和数组我们会经常遇到,今天讲一个相关问题,叫回文链表,还是和以前一样,先把代码提上来。
// need O(1) extra space
public static boolean isPalindrome3(Node head) {
if (head == null || head.next == null) {
return true;
}
Node n1 = head;
Node n2 = head;
while (n2.next != null && n2.next.next != null) { // find mid node
n1 = n1.next; // n1 -> mid
n2 = n2.next.next; // n2 -> end
}
// n1 中点
n2 = n1.next; // n2 -> right part first node
n1.next = null; // mid.next -> null
Node n3 = null;
while (n2 != null) { // right part convert
n3 = n2.next; // n3 -> save next node
n2.next = n1; // next of right node convert
n1 = n2; // n1 move
n2 = n3; // n2 move
}
n3 = n1; // n3 -> save last node
n2 = head;// n2 -> left first node
boolean res = true;
while (n1 != null && n2 != null) { // check palindrome
if (n1.value != n2.value) {
res = false;
break;
}
n1 = n1.next; // left to mid
n2 = n2.next; // right to mid
}
n1 = n3.next;
n3.next = null;
while (n1 != null) { // recover list
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
这段代码比传统方式要快很多,下面讲一下具体的流程:
if (head == null || head.next == null)
:首先检查链表是否为空或只包含一个节点,因为这种情况下链表必然是回文,所以直接返回true
。初始化两个指针
n1
和n2
都指向链表的头节点head
。这两个指针将用于查找链表的中间节点。进入一个循环,条件是
n2.next != null && n2.next.next != null
,循环中n1
每次移动一步,而n2
每次移动两步,这样当n2
到达链表尾部时,n1
恰好指向链表的中间节点(如果链表长度为奇数,则中间节点只有一个;如果链表长度为偶数,则中间节点有两个,这里n1
指向靠左的那一个)。将
n2
移动到n1
的下一个节点,这是为了将链表分为两部分:左边部分和右边部分。断开
n1
和n2
之间的连接,即将n1.next
设置为null
,从而分割链表为两个独立的部分。接下来是将右边部分(后半部分)的链表反转。使用三个指针
n1
、n2
、n3
分别指向当前节点、下一个节点和用于保存下下一个节点。通过循环,不断将当前节点的next
指针反向指向前一个节点,完成链表的反转。在反转完成后,
n3
会指向反转后链表的头节点,而n2
则指向左边部分链表的头节点。同时,n1
指向n3
,表示将n1
移动到反转链表的头节点位置。接下来,使用两个指针
n1
和n2
同时遍历左边部分和反转后的右边部分,比较它们的值是否相等。如果有任何不相等的情况出现,将res
设置为false
,表示链表不是回文。最后,需要将右边部分的链表再次反转回来,以恢复原始链表的结构。这是通过循环和指针操作实现的。
返回
res
,表示是否判断链表为回文。
假设输入链表为:1 -> 2 -> 3 -> 2 -> 1
-
首先找到链表的中间节点,
n1
指向节点 3。 -
将右边部分反转,得到链表:1 -> 2 -> 3 和 1 -> 2。
-
通过比较左右两边部分的值,发现它们是回文的。
-
最后将右边部分反转回来,得到原始链表:1 -> 2 -> 3 -> 2 -> 1。