简单回顾一下链表的结构~
1.单链表的节点结构
Class Node<V>{
V value;
Node next;
}
由以上结构的节点依次连接起来所形成的链叫单链表结构
2.双链表的节点结构
Class Node<V>{
V value;
Node next;
Node last;
}
由以上结构的节点依次连接起来所形成的链叫双链表结构
单链表和双链表结构只需要给定一个头部节点head,就可以找到剩下的所有的节点
【题目】给定一个单链表的头节点head,请判断该链表是否为回文结构。
【例子】1->2->3,返回true;1->2->2->1,返回true;1->2->3,返回false。
【要求】如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)。
思想:
假设有这么一组数,需要判断它是否是回文
解题思路:
1、先将原链表依次进栈(先进后出)
2、再将栈中元素依次出栈
3、每次出栈都与原链表从头节点开始比较,如果比较完都一致,说明是回文链表
代码如下:
/*
* 第一种思路:需要n个额外空间,将全部节点入栈,再依次出栈与原来节点顺序比较,
* 看是否是回文
* */
public static boolean isPalindrome1(Node head) {
Stack<Node> stack = new Stack<Node>();
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;
}
解题思路:(对称思想)
1、先用快慢指针法找到链表中间节点位置
快慢指针概念:
快慢指针中的快慢指的是移动的步长,即每次向前移动速度的快慢。例如可以让快指针每次沿链表向前移动2,慢指针每次向前移动1次。
2、将中间节点以后的部分放进栈中(右半部分)
3、右半部分出栈,与原链表左半部分依次进行比较,直到栈为空
4、如果比较结果一致,说明是回文链表
代码如下:
/*
* 第二种思路:需要n/2个额外空间,用快慢指针找到中间节点,将中间节点以后的节点放入栈中,
* 再出栈同时与原来中间节点以前的另一半部分比较,相同则是回文
* */
public static boolean isPalindrome2(Node head) {
if (head == null || head.next == null) {
return true;
}
Node right = head.next; //right指针最后指向中点右边第一个节点
Node cur = head;
while (cur.next != null && cur.next.next != null) {
right = right.next;
cur = cur.next.next;
}
Stack<Node> stack = new Stack<Node>();
while (right != null) { //将右半部分放入栈中
stack.push(right);
right = right.next;
}
while (!stack.isEmpty()) { //栈不为空时
if (head.value != stack.pop().value) { //栈中弹出来的节点和原链表从头节点开始对比,直到栈弹空
return false;
}
head = head.next;
}
return true;
}
解题思路:
1、先用快慢指针找到中点位置,我们想让快指针一次走两步,慢指针一次走一步,这样当快指针结束的时候,慢指针刚好来到了中点的位置。
2、将慢指针指向的节点去指向null,再将后面右半部分的指针逆序(为了后面首尾两头往中间靠拢的时候进行比较)
3、链表头尾各用一个引用去记,然后同时往中间遍历,每次走一步,用两边引用指向的进行比较,直到一边为null,则停止,如果比较结果都为true,则说明是回文。注意最后返回结果前将逆序指针恢复原状。
完整代码如下,包含测试用例:
public class IsPalindromeList {
public static void main(String[] args) {
Node head = new Node(1);
Node n1 = new Node(2);
Node n2 = new Node(3);
Node n3 = new Node(2);
Node n4 = new Node(1);
head.next = n1;
n1.next = n2;
n2.next = n3;
n3.next = n4;
n4.next = null;
//System.out.println(isPalindrome1(head)); //方法1
//System.out.println(isPalindrome2(head)); //方法2
System.out.println(isPalindrome3(head)); //方法3
}
public static class Node {
public int value;
public Node next;
public Node(int data) {
this.value = data;
}
}
/*
* 第三种思路:需要O(1)个额外空间,思路见最上方(推荐使用)
* 解题思路:
* 1、先用快慢指针找到中点位置,我们想让快指针一次走两步,慢指针一次走一步,这样当快指针结束的时候,慢指针刚好来到了中点的位置。
* 2、将慢指针指向的节点去指向null,从中点处往下遍历的时候将后面的指针逆序
* 3、链表头尾各用一个引用去记,然后同时往中间遍历,每次走一步,直到一边为null,则停止,期间两边引用指向的值相同,则说明是回文。最后返回结果前将逆序指针恢复原状。
* */
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下一个节点,也就是开始逆序
n1.next = null; //将慢指针的下个节点指向null
Node n3 = null;
while (n2 != null) { //右半部分都进行逆序
n3 = n2.next; //n3用来保存下一个节点
n2.next = n1;
n1 = n2;
n2 = n3;
}
n3 = n1; //n3 --> 保存最后一个节点
n2 = head; //n2 --> 左边第一个节点
boolean res = true;
while (n1 != null && n2 != null) { //检查回文
if (n1.value != n2.value) {
res = false; //结果不一样就是false,否则为true
break;
}
n1 = n1.next; //n1从左边往中间走
n2 = n2.next; //n2从右边往中间走
}
n1 = n3.next;
n3.next = null;
while (n1 != null) { //将逆序复原
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
}
第三种方法可以好好钻研一下,面试官看了直呼内行!!!!