题目:给定一个单链表头结点 head,请判断该链表是否为回文结构
例子:1->2->1,返回true;1->2->2->1,返回true;1->2->3,返回false。
要求:如果链表长度为N,时间复杂度达到 O(N),额外空间复杂度达到 O(1)
分析:想到三种方法
-
压入栈中,然后遍历同时弹栈,看节点内容是否想等
-
和第一种一样不过只让链表后半部分压栈,然后遍历比对
-
第三种:空间复杂度 O(1)
step1:先用快慢指针(slow、fast)找到中点(偶数为对称轴前一个,奇数即为中点)step2:然后将后面的链表反转:
1)偶数时:1 -> 2 -> 3 <- 4 <- 5 <- 6
2)奇数时:1 -> 2 -> 3 -> 4 <- 5 <- 6 <- 7step3:然后分别遍历左右链表比对即可得出结果
step4:别忘了将链表复原
法一:链表全部压栈
// 把整个链表压栈,然后遍历链表和弹栈结果是否一致
public static boolean isPalindrome1(ListNode head) {
if (head == null || head.next == null) return true;
Stack<ListNode> stack = new Stack<>();
ListNode tmp = head;
while (tmp != null) {
stack.push(tmp);
tmp = tmp.next;
}
while (head != null && stack.pop().val == head.val) {
head = head.next;
}
return head == null ? true : false;
}
法二:一半压栈
如何找到中点:快慢指针 /slow/fast,条件如何取。有三种常见初始状态(但至少要两个节点)
- slow = fast = head:都指向头结点
- slow = head; fast = head.next; :慢指向头结点,快指向第二节点
- slow = head.next; fast = head; :慢指向第二节点,快指向头结点
然后依次讨论判断的条件是 fast.next!= null && fast.next.next != null
-
slow = fast = head; 时 EX1: 1 2 开始都指向第 1 明显 fast 一步都不能动 指向对称轴前一个 EX2: 1 2 3 4 由判断条件得出fast可以走1步,那么slow指向2 指向对称轴前一个 EX3: 1 2 3 4 5 6 fast可走2步,slow指向3 对称轴前一个 EX4: 1 2 3 fast可以走1步,slow指向2 指向中点位置 EX5: 1 2 3 4 5 fast可以走2步,slow指向3 指向中点位置 EX6: 1 2 3 4 5 6 7 fast可走3步,slow指向4 指向中点位置
-
slow = head; fast = head.next; 时 EX1:链表:1 2 slow指向1,fast指向2 明显 fast 一步都不能动 然后此时指向对称轴前一个 EX2:链表:1 2 3 4 由判断条件得出fast可以走1步,那么slow指向2 也是指向对称轴前一个 EX3: 1 2 3 4 5 6 fast可走2步,slow指向3 对称轴前一个 EX4:链表:1 2 3 fast可以走0步,slow指向1 指向中点前一个位置 EX5:链表:1 2 3 4 5 fast可以走1步,slow指向2 指向中点前一个位置 EX6: 1 2 3 4 5 6 7 fast可走2步,slow指向3 指向中点位置前一个位置
-
slow = head.next; fast = head; 时 EX1:链表:1 2 slow指向2,fast指向1 明显 fast 一步都不能动 然后此时指向对称轴后一个 EX2:链表:1 2 3 4 由判断条件得出fast可以走1步,那么slow指向3 也是指向对称轴后一个 EX3: 1 2 3 4 5 6 fast可走2步,slow指向3 对称轴后一个 EX4:链表:1 2 3 fast可以走1步,slow指向3 指向中点后一个位置 EX5:链表:1 2 3 4 5 fast可以走2步,slow指向4 指向中点后一个位置 EX6: 1 2 3 4 5 6 7 fast可走3步,slow指向5 指向中点后一个位置
最后得出结论:判断条件是 fast.next!= null && fast.next.next != null
- slow = fast = head;
偶数:对称轴前一个
奇数:中点
- slow = head; fast = head.next;
偶数:对称轴前一个
奇数:中点前一个
- slow = head.next; fast = head;
偶数:对称轴后一个
奇数:中点后一个
// 很容易得出,我们需要后一半压栈所以选择第三种快慢指针方案
public static boolean isPalindrome2(ListNode head) {
if (head == null || head.next == null) return true;
ListNode slow = head.next;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
// 此时slow指向中点或者对称轴的下一个
Stack<ListNode> stack = new Stack<>();
while (slow != null) {
stack.push(slow);
System.out.print(slow.val + "_");
slow = slow.next;
}
while (!stack.isEmpty()) {
if (head.val != stack.pop().val) {
return false;
}
head = head.next;
}
return true;
}
法三:
这里需要用到反转链表的模板,模板如下
// 加入 head 为链表的头结点
ListNode pre = head; // 即指向头结点
ListNode last = head.next; // 即指向头结点下一个
ListNode tmp = null;
pre.next = null;
while (last != null) {
tmp = last.next;
last.next = pre;
pre = last;
last = tmp;
}
// 最后循环完后的结果是 pre 指向反转的首节点,last、tmp 指向 null
// 用偶数个举例,如链表为 1 -> 2 -> 3 -> 4 -> 5 -> 6
public static boolean isPalindrome3(ListNode head) {
if (head == null || head.next == null) return true;
// step1:找到中点位置,偶数为对称轴前一个,奇数为中点
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
// step2:反转后半部分链表
// 此时 slow 指向中点或者对称轴的前一个,也就是相当于反转部分的头结点
// 即 slow 指向 3,接下来要反转后面部分得到:1 -> 2 -> 3 <- 4 <- 5 <- 6
fast = slow.next;
ListNode tmp = null;
slow.next = null;
while (fast != null) {
tmp = fast.next;
fast.next = slow;
slow = fast;
fast = tmp;
}
tmp = slow; // 指向最后一个节点(或者后部分链表的头结点)这里后面会用到
// step3:将两边链表分别遍历比对
// 此时slow指向反转链表的首节点,也就是原链表的最后一个
// 1 -> 2 -> 3 <- 4 <- 5 <- 6 slow 指向 6
// 然后我们依次遍历两边的链表
fast = head;
boolean ans = true;
while (fast != null && slow != null) {
// System.out.print("fast:" + fast.val + ",slow:" + slow.val + "\n");
if (fast.val != slow.val) {
ans = false;
break;
}
fast = fast.next;
slow = slow.next;
}
// step4:链表复原
// 此时 slow 指向 3,fast 指向 null
// 如果是奇数个 slow、fast 都指向 null,所以要重新赋值
slow = tmp;
fast = slow.next;
while (fast != null) {
tmp = fast.next;
fast.next = slow;
slow = fast;
fast = tmp;
}
return ans;
}
最后附上全部代码
package LinkedList;
import java.util.Stack;
/**
* @ Date: 2021/2/10 - 13:19
* @ Description: LinkedList
* @ Version: 1.0
*/
// https://leetcode-cn.com/problems/palindrome-linked-list-lcci/
public class isPalindromeLinkedList_回文链表 {
static class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}
static void printLinkedList(ListNode head) {
System.out.print("Linked List: ");
while (head != null) {
System.out.print(head.val + " ");
head = head.next;
}
System.out.println();
}
// 把整个链表压栈,然后遍历链表和弹栈结果是否一致
public static boolean isPalindrome1(ListNode head) {
if (head == null || head.next == null) return true;
Stack<ListNode> stack = new Stack<>();
ListNode tmp = head;
while (tmp != null) {
stack.push(tmp);
tmp = tmp.next;
}
while (head != null && stack.pop().val == head.val) {
head = head.next;
}
return head == null ? true : false;
}
// 半个链表压栈
public static boolean isPalindrome2(ListNode head) {
if (head == null || head.next == null) return true;
ListNode slow = head.next;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
// 此时slow指向中点或者对称轴的下一个
Stack<ListNode> stack = new Stack<>();
while (slow != null) {
stack.push(slow);
// System.out.print(slow.val + "_");
slow = slow.next;
}
while (!stack.isEmpty()) {
if (head.val != stack.pop().val) {
return false;
}
head = head.next;
}
return true;
}
// 用偶数个举例,如链表为 1 -> 2 -> 3 -> 4 -> 5 -> 6
public static boolean isPalindrome3(ListNode head) {
if (head == null || head.next == null) return true;
// step1:找到中点位置,偶数为对称轴前一个,奇数为中点
ListNode slow = head;
ListNode fast = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
// step2:反转后半部分链表
// 此时 slow 指向中点或者对称轴的前一个,也就是相当于反转部分的头结点
// 即 slow 指向 3,接下来要反转后面部分得到:1 -> 2 -> 3 <- 4 <- 5 <- 6
fast = slow.next;
ListNode tmp = null;
slow.next = null;
while (fast != null) {
tmp = fast.next;
fast.next = slow;
slow = fast;
fast = tmp;
}
tmp = slow; // 指向最后一个节点(或者后部分链表的头结点)这里后面会用到
// step3:将两边链表分别遍历比对
// 此时slow指向反转链表的首节点,也就是原链表的最后一个
// 1 -> 2 -> 3 <- 4 <- 5 <- 6 slow 指向 6
// 然后我们依次遍历两边的链表
fast = head;
boolean ans = true;
while (fast != null && slow != null) {
// System.out.print("fast:" + fast.val + ",slow:" + slow.val + "\n");
if (fast.val != slow.val) {
ans = false;
break;
}
fast = fast.next;
slow = slow.next;
}
// step4:链表复原
// 此时 slow 指向 3,fast 指向 null
// 如果是奇数个 slow、fast 都指向 null,所以要重新赋值
slow = tmp;
fast = slow.next;
while (fast != null) {
tmp = fast.next;
fast.next = slow;
slow = fast;
fast = tmp;
}
return ans;
}
public static void main(String[] args) {
ListNode head = null;
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head));
head = new ListNode(1);
head.next = new ListNode(2);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head));
head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head));
head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head));
head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(2);
head.next.next.next.next = new ListNode(3);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head));
head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(3);
head.next.next.next.next.next = new ListNode(2);
head.next.next.next.next.next.next = new ListNode(1);
printLinkedList(head);
System.out.print(isPalindrome1(head) + " | ");
System.out.print(isPalindrome2(head) + " | ");
System.out.println(isPalindrome3(head));
}
}
// Output
Linked List:
true | true | true
Linked List: 1 2
false | false | false
Linked List: 1 2 1
true | true | true
Linked List: 1 2 3 4
false | false | false
Linked List: 1 2 3 2 3
false | false | false
Linked List: 1 2 3 4 3 2 1
true | true | true