最近在学习一些算法相关的知识,遇到回文链表,接下来将讲解三种不同的方式解回文链表
先创建一个 Node 类
public class Node {
public int value;
public Node next;
public Node(int value) {
this.value = value;
}
}
方法一
我们知道单链表是有一个 next 指针指向下一个节点,末尾的节点指向 null ,我们首先借助栈来判断是否时回文链表 栈最主要的特点:先进后出 实现思路:
- 遍历链表,依次将链表的节点入栈
- 再次遍历链表,将栈中节点一次弹栈
- 如果每一次遍历的节点与弹栈的节点相同,说明是回文链表
- 如果在遍历过程中有一次不同,那么就不是回文链表
代码演示
public static boolean isPalindrome1(Node head) {
if (head == null || head.next == null) {
return true;
}
Stack<Node> stack = new Stack<>();
Node cur = head;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
cur = head;
while (cur != null) {
if (cur.value != stack.pop().value) {
return false;
}
cur = cur.next;
}
return true;
}
方法二
使用快慢指针 + 栈的方式 既然方法一中已经有栈的方法了,那么方法二中为什么还要使用栈呢?原因:方法一中是将所有的链表节点压入栈中,方法二是将一半的节点压入栈中。
实现思路:
- 首先使用快慢指针找到中间节点
- 将中间节点的右边压入栈
- 将栈中的节点一次弹栈,同时从头遍历节点,两者进行节点比较
- 若判断节点全部相同,则说明是回文链表,否是不是回文链表
代码演示
public static boolean isPalindrome2(Node head) {
if (head == null || head.next == null) {
return true;
}
Node slow = head.next;
Node fast = head;
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.empty()) {
if (slow.value != stack.pop().value) {
return false;
}
slow = slow.next;
}
return true;
}
补充:
在利用快慢指针的时候,我们知道如果链表的长度是奇数个,那么慢指针将会指向最中间的节点,如果是偶数个,慢指针将会指向最中间两个的左边哪个。当我们在面试的时候如果要使用快慢指针,那么慢指针针对题目要求走到那一步更为合适,是指向最中间还是最中间的左边,亦或是最中间的右边,这都需要我们在下面多家练习,练习多了自然在面试中遇到不会出现问题
方法三
使用快慢指针 + 反转链表的方式。降低空间复杂度 O(1)
实现思路:
- 使用快慢指针遍历一遍链表,此时慢指针指向链表重点的位置
- 将慢指针右边的链表进行反转,指向中间节点,中间节点指向
null - 从左右两端进行链表遍历并进行等值判断
- 将原来链表还原成原来的样子
代码实现
public static boolean isPalindrome3(Node head) {
if (head== null || head.next == null) {
return true;
}
Node n2 = head;
Node n1 = head;
while (n2.next != null && n2.next.next != null) {
n2 = n2.next.next;
n1 = n1.next;
}
n2 = n1.next;
n1.next = null;
Node n3 = null;
while (n2 != null) {
n3 = n2.next;
n2.next = n1;
n1 = n2;
n2 = n3;
}
n3 = n1;
n2 = head;
boolean res = true;
while (n1 != null && n2 != null) {
if (n1.value != n2.value) {
res = false;
break;
}
n1 = n1.next;
n2 = n2.next;
}
n1 = n3.next;
n3.next = null;
while (n1 != null) {
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
说明: 上面的代码中,有两次用到了反转链表这个块的知识。我们在刷一些算法题的过程中,有时候也会遇到反转链表1和反转链表2,但是那些就明明白白的告诉你就是一道反转链表的题目。 但是有时候将反转链表的思想利用的其他的算法中,成为解题的一小步的话,那么我们还能不能很好的利用反转链表这块的知识了。也会我们想得到但也并不能很好的给题目解出来。因此经典的基础题目还深入理解透彻,并能轻轻松松的写出来,才可以达到在面试时遇到算法以至于不那么被动
像在接一些比较难的题目的时候,需要用到经典的小思想,就比如上面的反转链表,我称这种为半成品,多掌握一些半成品,并能熟练运用非常有用。
|