题目:
判断一个链表是否为回文结构。如果链表长度为N,时间复杂度为O(N),空间复杂度为O(1)
分析:
方法1:使用栈,需要O(N)的额外空间
1.将链表中节点的值全部存入栈中,
2.根据栈先进后出的特性,和原链表元素从头进行比较
方法2:使用栈和快慢指针,相较于方法1减少一半额外空间
1.使用快慢指针找到链表的中点(代码中找的是链表中点的下一个位置)
2.根据栈先进后出的特性,将链表后半部分存入栈中
3.将链表的前半部分和后半部分进行比较
方法3:使用快慢指针,并且将链表右半部分进行逆序,需要O(1)的额外空间
1.使用快慢指针找到链表的中点
2.将中点后的部分进行逆序操作
3.将逆序后的右半部分和左半部分进行比较
代码实现:
public class IsPalindromeList {
public static class Node {
int value;
Node next;
public Node(int value) {
this.value = value;
}
}
// 需要O(N)的额外空间:使用栈
public static boolean isPalindrome1(Node head) {
Stack<Node> stack = new Stack<>();
Node cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
while (head != null) {
if (head.value != stack.pop().value) {
return false;
}
head = head.next;
}
return true;
}
// 需要O(N)/2的额外空间:使用栈和快慢指针
public static boolean isPalindrome2(Node head) {
if (head == null || head.next == null) {
return true;
}
Node n1 = head.next;
Node n2 = head;
// right用于寻找链表的中点下一个位置
while (n2.next != null && n2.next.next != null) {
n1 = n1.next;
n2 = n2.next.next;
}
Stack<Node> stack = new Stack<>();
while (n1 != null) {
stack.push(n1);
n1 = n1.next;
}
while (!stack.isEmpty()) {
if (head.value != stack.pop().value) {
return false;
}
head = head.next;
}
return true;
}
// O(1)的额外空间:快慢指针并且将链表逆序
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){
n1 = n1.next; // n1指针移动到链表中点
n2 = n2.next.next; // n2指针移动到链表末端
}
n2 = n1.next; // 此时n2指向右半部分的头结点
n1.next = null; //将n1指向的中点与右半部分断开
Node n3 = null;
// 将右半部分链表进行逆序操作
while (n2 != null){
n3 = n2.next; // n3用于储存下一轮要调转指针的节点
n2.next = n1; // n2为此时需要调转指针的节点,将其指针往回指
n1 = n2; // 调转完n2的指针后,n2就变为下一个需要调转的节点指向的节点
n2 = n3; // 将n3赋给n2, 进行下一轮调转
}
n3 = n1; // 此时的n1是右半部分链表的最后一个节点,使用n3对其进行保持以便后续恢复链表
n2 = head; // 将n2指向左半部分的头节点
boolean res = true;
// 对左半部分和逆序后的右半部分进行比较
while (n1 != null && n2 != null){
if (n1.value != n2.value){
res = false;
break;
}
n1 = n1.next; // left->mid
n2 = n2.next; // right->mid
}
// 恢复右半部分
n1 = n3.next; // n1指向右半部分倒数第二个节点
n3.next = null; // 将右半部分最后一个节点断开
while (n1 != null){
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
}
tips:
方法2和方法3快慢指针的初始化有所不同,需要根据题目的不同进行变化。