题目
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构(以中间节点为轴,两边的两个小链表的值是对称的)。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
测试样例:
1->2->2->1
返回:true
思路1:双指针遍历数组——从两头到中间
- 先将链表遍历一次,将所有元素存放在数组(普通数组/JDK内置的ArrayList动态数组)中。
- 定义left索引下标从数组第一个元素开始向后遍历,定义right索引下标从数组最后一个元素开始向前遍历。
- 当left <= right时进行判断比较:left位置的元素值和right位置的元素值是否相等,若相等,符合回文结构要求,继续判断;若不等,则返回false,不是回文结构。
代码1
牛客网OR36:普通数组
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class PalindromeList {
public boolean chkPalindrome(ListNode A) {
//判断边界条件
if(A == null || A.next == null) {
return true;
}
//得到链表长度size
ListNode node = A;
int size = 0;
while(node != null) {
size++;
node = node.next;
}
//将链表的元素存放到数组中
int[] ret = new int[size];
for(int i = 0; i < size; i++) {
ret[i] = A.val;
A = A.next;
}
//双指针遍历数组,从两头到中间
int left = 0, right = size - 1;
while(left <= right) {
if(ret[left] != ret[right]) {
return false;
}
left++;
right--;
}
return true;
}
}
剑指Offer II 027.回文链表:ArrayList动态数组
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
List<Integer> arr = new ArrayList<Integer>();
//将链表的值复制到数组中
ListNode node = head;
while(node != null) {
arr.add(node.val);
node = node.next;
}
//使用双指针判断是否回文
int left = 0, right = arr.size() - 1;
while(left <= right) {
if(!arr.get(left).equals(arr.get(right))) {
return false;
}
left++;
right--;
}
return true;
}
}
思路2
- 在不要求空间复杂度的情况下,直接采用头插法新建一个新链表,相当于将原链表进行了反转。
- 将原链表和新链表都从头开始遍历,比较。
代码2
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public boolean isPalindrome(ListNode head) {
//需要创建一个新链表,新链表是原链表的倒置
ListNode newHead = reverse(head);
//同步遍历原链表和新链表,当发现值不同时,return fasle
while(head != null) {
if(head.val != newHead.val) {
return false;
}
head = head.next;
newHead = newHead.next;
}
return true;
}
//在力扣或牛客上,当一个方法无法完全解决问题时,将多个步骤拆分为多个方法
//此处无需判空,这个方法是我们自己调用的,就能保证它非空
public ListNode reverse(ListNode head) {
ListNode newHead = null;
while(head != null) {
ListNode node = new ListNode(head.val);
node.next = newHead; //此处的=表示赋值,将当前头节点newHead的地址赋值给node.next(所有引用数据类型存储的都是地址)
newHead = node;
head = head.next;
}
return newHead;
}
}
思路3:找中间节点+链表反转
找到原链表的中间节点,以中间节点为轴,划分为两个子链表,将后半部分子链表倒置,同步和第一个子链表比较值。
原链表:1->2->3->2->1。
子链表A:1->2。
倒置后的子链表B:1->2->3。
终止条件:A != null && B != null。
代码3
import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class PalindromeList {
public boolean chkPalindrome(ListNode A) {
//找到中间节点
ListNode middle = middleNode(A);
//倒置后半部分链表
ListNode B = reverse(middle);
while(A != null && B != null) {
if(A.val != B.val) {
return false;
}
A = A.next;
B = B.next;
}
return true;
}
//快慢指针法找中间节点
public ListNode middleNode(ListNode head) {
ListNode low = head, fast = head;
while(fast != null && fast.next != null) {
low = low.next;
fast = fast.next.next;
}
return low;
}
//链表倒置(递归)
public ListNode reverse(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode tmpNext = head.next;
//将第二个节点以及其之后的所有节点的倒置工作交给reverse方法
ListNode newHead = reverse(head.next);
//处理头节点的情况
tmpNext.next = head;
head.next = null;
return newHead;
}
}