判断一个链表是否为回文结构
- 方法一 : 使用一个栈(O(n)的空间复杂度)
- 方法二 : 使用快慢指针,以及O(n/2)的空间复杂度
- 方法三 : 使用快慢指针并反转链表(不需要额外的空间复杂度)
方法一 : 使用一个栈(O(n)的空间复杂度)
这个方法很简单,先遍历一遍链表,把整个链表都压入栈中,然后再遍历一遍,每次从栈顶拿出一个元素进行比较,如果所有元素都相同,则返回true,否则只要有一个不同就返回false。
//第一种方法 使用O(n)的空间
public static boolean isPalindromeList_1(Node head){
if(head == null || head.next == null)return true;
Stack<Node>stack = new Stack<>();
Node node = head;
while(node != null){
stack.push(node);
node = node.next;
}
node = head;
while(node != null){
if(node.value != stack.pop().value){
return false;
}
node = node.next;
}
return true;
}
方法二 : 使用快慢指针,以及O(n/2)的空间复杂度
这个就是在第一种方法的基础上进行简单的改进,首先定义两个指针fast和slow ,每次让fast指针走两步,slow指针走一步,然后当fast指针走完的时候,slow指针刚好来到中点,此时把链表的后半部分压入栈中,然后拿栈中的元素依次和链表的前半部分比较,就可以得到结果。 在coding 的过程中,要注意链表长度奇偶的不同,奇数的时候,slow指针指到中间位置的下一个位置,偶数的时候也要slow指针知道中间位置的下一个位置,一开始的时候slow = head.next,还有就是要注意fast移动的过程中,要做两个判断,防止空指针异常,具体奇偶的过程看下图。
代码实现:
//第二种方法 使用O(n/2)的空间
public static boolean isPalindromeList_2(Node head){
if(head == null || head.next == null)return true;
Node fast = head;
Node slow = head.next; //保证和不管是奇数还是偶数 都会来到中间位置的下一个位置(就是为了把后半部分压入栈中)
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
Stack<Node>stack = new Stack<>();
while(slow != null){
stack.push(slow);
slow = slow.next;
}
slow = head;
while(!stack.isEmpty()){
if(slow.value != stack.pop().value)return false;
slow = slow.next;
}
return true;
}
方法三 : 使用快慢指针并反转链表(不需要额外的空间复杂度)
方法三的思想也要使用快指针和慢指针,快指针一次走两步,慢指针一次走一步,当快指针走完的时候,慢指针来到中间的位置(或者中间的前一个位置(偶数的情况(和上面的方法是不同的(上面的是慢指针在中间的后一个位置)))),然后,要用到链表的反转(链表反转不清楚的可以看一下这篇博客),此时,我们将后半部分链表反转,然后使用两个指针分别从 头部和尾部位置开始比较,直到其中一个为空。 当然,不管返回true还是false,我们最后都将反转的链表的结构反转回来。
在coding的过程中,也要注意奇数和偶数的情况,奇数的时候,slow指针正好来到中间结点,偶数的时候来到中间结点的前一个节点。
还有就是反转的细节,使用到了一个额外的next结点变量,在第一次反转的过程中,用来表示当前要反转的结点的下一个节点,还有就是在第一次反转的过程中,我没有额外的再声明pre结点变量,直接使用slow来代替pre结点变量,后面的第二次反转也是重复利用了结点变量。
具体奇偶的过程可以看下图
代码实现
//第三种方法,不使用额外的空间
public static boolean isPalindromeList_3(Node head){
if(head == null || head.next == null)return true;
Node slow = head;
Node fast = head; //这里快指针和慢指针的赋值是为了 不管是奇数还是偶数都保证 快指针走完的之后,慢指针来到中间位置(奇数)或者中间位置的前一个位置(偶数)
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
fast = slow.next; //快指针来到慢指针的下一个节点
slow.next = null; //慢指针指向null(为了反转链表)
Node next = null; //代表反转链表的下一个结点
//反转后半部分
while(fast != null){
next = fast.next;
fast.next = slow; //用slow 来充当反转链表中的pre节点
slow = fast;
fast = next;
}
next = slow; //反转完之后,slow成为了最后一个结点(可以看做是后半部分的头结点) 此时用next保存这个结点
fast = head; //这是为了来比较
boolean res = true;
while(slow != null && fast != null){ //从两边开始比较
if(slow.value != fast.value){
res = false;
break;
}
slow = slow.next;
fast = fast.next;
}
//不管是false还是true都要还原链表
slow = next.next; //用slow来记录最后一个结点的下一个结点
next.next = null; //最后一个节点的下一个节点赋值为null(反转开始(第一步))
while(slow != null){
fast = slow.next;
slow.next = next; //用next来表示pre
next = slow;
slow = fast;
}
return res;
}
再贴上总的测试代码
import java.util.Stack;
/**
* 判断一个链表是不是一个回文结构
* 三种方法:
* 第一种 使用一个栈O(n)的空间复杂度 将整个链表压栈,然后再遍历一遍比对
* 第二种 使用半个栈(O(n/2))的空间复杂度 快指针一次走两步,慢指针一次走一步 然后将后面的部分压栈
* 第三种 不使用额外的空间,快指针一次走两步,慢指针一次走一步,走完之后,将后半部分 链表反转,然后使用两个指针从开头和最后开始比较(注意比较完之后恢复链表)
*/
public class IsPalindromeList {
private static class Node{
public int value;
public Node next;
public Node(int value) {
this.value = value;
}
}
//第一种方法 使用O(n)的空间
public static boolean isPalindromeList_1(Node head){
if(head == null || head.next == null)return true;
Stack<Node>stack = new Stack<>();
Node node = head;
while(node != null){
stack.push(node);
node = node.next;
}
node = head;
while(node != null){
if(node.value != stack.pop().value){
return false;
}
node = node.next;
}
return true;
}
//第二种方法 使用O(n/2)的空间
public static boolean isPalindromeList_2(Node head){
if(head == null || head.next == null)return true;
Node fast = head;
Node slow = head.next; //保证和不管是奇数还是偶数 都会来到中间位置的下一个位置(就是为了把后半部分压入栈中)
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
Stack<Node>stack = new Stack<>();
while(slow != null){
stack.push(slow);
slow = slow.next;
}
slow = head;
while(!stack.isEmpty()){
if(slow.value != stack.pop().value)return false;
slow = slow.next;
}
return true;
}
//第三种方法,不使用额外的空间
public static boolean isPalindromeList_3(Node head){
if(head == null || head.next == null)return true;
Node slow = head;
Node fast = head; //这里快指针和慢指针的赋值是为了 不管是奇数还是偶数都保证 快指针走完的之后,慢指针来到中间位置(奇数)或者中间位置的前一个位置(偶数)
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
fast = slow.next; //快指针来到慢指针的下一个节点
slow.next = null; //慢指针指向null(为了反转链表)
Node next = null; //代表反转链表的下一个结点
//反转后半部分
while(fast != null){
next = fast.next;
fast.next = slow; //用slow 来充当反转链表中的pre节点
slow = fast;
fast = next;
}
next = slow; //反转完之后,slow成为了最后一个结点(可以看做是后半部分的头结点) 此时用next保存这个结点
fast = head; //这是为了来比较
boolean res = true;
while(slow != null && fast != null){ //从两边开始比较
if(slow.value != fast.value){
res = false;
break;
}
slow = slow.next;
fast = fast.next;
}
//不管是false还是true都要还原链表
slow = next.next; //用slow来记录最后一个结点的下一个结点
next.next = null; //最后一个节点的下一个节点赋值为null(反转开始(第一步))
while(slow != null){
fast = slow.next;
slow.next = next; //用next来表示pre
next = slow;
slow = fast;
}
return res;
}
//打印链表的函数
public static void printList(Node node) {
System.out.print("List: ");
while (node != null) {
System.out.print(node.value + " ");
node = node.next;
}
System.out.println();
}
public static void main(String[] args) {
System.out.println("---------测试奇数个结点-------------");
Node head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(2);
head.next.next.next.next = new Node(1);
printList(head);
System.out.println(isPalindromeList_1(head));
System.out.println(isPalindromeList_2(head));
System.out.println(isPalindromeList_3(head));
System.out.println("---------测试偶数个结点-------------");
Node head2 = new Node(1);
head2.next = new Node(2);
head2.next.next = new Node(2);
head2.next.next.next = new Node(1);
printList(head2);
System.out.println(isPalindromeList_1(head2));
System.out.println(isPalindromeList_2(head2));
System.out.println(isPalindromeList_3(head2));
System.out.println("---------测试非回文串-------------");
Node head3 = new Node(1);
head3.next = new Node(2);
head3.next.next = new Node(3);
head3.next.next.next = new Node(1);
printList(head3);
System.out.println(isPalindromeList_1(head3));
System.out.println(isPalindromeList_2(head3));
System.out.println(isPalindromeList_3(head3));
System.out.println("--------------测试一些特殊例子------------");
Node head4 = new Node(1);
head4.next = new Node(1);
printList(head4);
System.out.println(isPalindromeList_1(head4));
System.out.println(isPalindromeList_2(head4));
System.out.println(isPalindromeList_3(head4));
}
}
运行结果