问题
如果字符串存储在单链表中,怎么快速判断这个字符串是否是回文字符串?
知识点
快慢指针、链表反转
实现思路
一般方法 复杂度为O(n²)
- 如果字符串存储在数组中,那么只需要判断第一个和最后一个这么一一对应下去,判断字符是否相同,还是比较简单的。
- 如果单链表中存储也参照这种方法,那么可以定义两个指针,一个初始指向头部,一个初始指向尾部,后续依次一个向后指一个依次向前指。这样也能判断是否是回文字符串。但是对于单链表,查找前继节点,需要遍历链表,所以复杂度是O(n²)
复杂度为O(n)的方法
- 如果使用单链表存储,那么第一步需要找到链表的中间节点,这一步需要用到快慢指针
- 第二步需要将单链表的后半部分进行反转,单链表的反转
代码
定义链表结构
/**
* 单链表的节点类
*
* @author StoneYu
* @date 2022/09/25
*/
@Data
@AllArgsConstructor
public class SinglyLinedNode<T> {
T data;
SinglyLinedNode<T> next;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public SinglyLinedNode<T> getNext() {
return next;
}
public void setNext(SinglyLinedNode<T> next) {
this.next = next;
}
}
@Data
public class SinglyLinkedList<T> {
/**
* 单链表的头指针
*/
SinglyLinedNode<T> head;
/**
* 从尾部添加一个节点
*
* @param t t
*/
public void add(SinglyLinedNode<T> t) {
if (t == null) {
return;
}
if (head == null) {
head = t;
return;
}
SinglyLinedNode<T> couser = head;
while (couser.next != null) {
couser = couser.next;
}
couser.next = t;
}
/**
* 打印链表内容
*
* @return {@link String}
*/
public String getString() {
if (head == null) {
return null;
}
StringBuilder stringBuilder = new StringBuilder();
SinglyLinedNode<T> couser = head;
stringBuilder.append("链表:" + (couser.getData()==null?"null":couser.getData()));
while (couser.next != null) {
couser = couser.next;
stringBuilder.append("->" + (couser.getData()==null?"null":couser.getData()));
}
return stringBuilder.toString();
}
}
从指定的位置反转单链表
/**
* 从第 index+1 的元素开始反转,0反转整个链表
*
* @param singlyLinkedList 单链表
* @param index 下标 >0
*/
static SinglyLinkedList reverseALinkedList(SinglyLinkedList singlyLinkedList, int index) {
//空、或者只有1个元素
if (singlyLinkedList.head == null || singlyLinkedList.head.next == null) {
return singlyLinkedList;
}
if (index <= 0) {
index = 0;
}
//反转尾部的链表进行比较
SinglyLinedNode cursor = new SinglyLinedNode(null, singlyLinkedList.head);
int temp = 0;
//begin作为一个伪头结点
SinglyLinedNode begin = new SinglyLinedNode(null, singlyLinkedList.head);
if (index == 0) {
singlyLinkedList.head = begin;
}
//mid作为截取队列的的首个节点
SinglyLinedNode mid = null;
//end是需要进行头插的节点
SinglyLinedNode end = null;
while (cursor.next != null) {
//遍历链表
cursor = cursor.next;
temp++;
if (temp == index) {
begin = cursor;
}
if (temp - 1 == index) {
mid = cursor;
end = cursor.next;
}
//反转半个链表,就地逆置法
if (temp > index && end != null) {
//将需要逆序的元素,直接作为头结点的后继节点
mid.next = end.next;
end.next = begin.next;
begin.next = end;
//保持遍历的顺序,将游标往后移动
cursor = mid;
//如果还有需要进行头插的元素,则end指针需要往后移动一下
if (mid.next != null) {
end = mid.next;
}
}
}
if (index == 0) {
singlyLinkedList.head = begin.next;
}
SinglyLinkedList<Object> objectSinglyLinkedList = new SinglyLinkedList<>();
objectSinglyLinkedList.setHead(begin.next);
return objectSinglyLinkedList;
}
最终方法
/**
* 单链表结构判断字符串是否是回文字符串
* 一般方法 复杂度为O(n²)
* 1. 如果字符串存储在数组中,那么只需要判断第一个和最后一个这么一一对应下去,判断字符是否相同,还是比较简单的。
* 2. 如果单链表中存储也参照这种方法,那么可以定义两个指针,一个初始指向头部,一个初始指向尾部,后续依次一个向后指一个依次向前指。这样也能判断是否是回文字符串。但是对于单链表,查找前继节点,需要遍历链表,所以复杂度是O(n²)
* 复杂度为O(n)的方法
* 1. 如果使用单链表存储,那么第一步需要找到链表的中间节点,这一步需要用到快慢指针
* 2. 第二步需要将单链表的后半部分进行反转,单链表的反转
*
* @author StoneYu
* @date 2022/09/25
*/
public class TestHuiWenZiFuChuan {
public static void main(String[] args) {
String testStr = "1";
SinglyLinkedList<Character> singlyLinkedList = transStringToLinkedList(testStr);
System.out.println(singlyLinkedList.getString());
//查找链表的中间节点的下标 快慢指针
SinglyLinedNode slow = singlyLinkedList.head;
SinglyLinedNode quick = singlyLinkedList.head;
int index = 1;
while (quick.next != null) {
slow = slow.next;
quick = quick.next;
if (quick.next == null) {
break;
}
quick = quick.next;
index++;
}
SinglyLinkedList singlyLinkedList1 = reverseALinkedList(singlyLinkedList, index);
System.out.println("中间位置为:"+index);
System.out.println("从中间反转后原链表:" + singlyLinkedList.getString());
System.out.println("反转后截取的链表:" + singlyLinkedList1.getString());
//定义两个指针挨个比较
System.out.println("需要比较的节点数量中间数为"+(index%2==0?"偶数则-1":"奇数则不变")+":"+(index%2==0?index-1:index));
int needConpare=index%2==0?index-1:index;
int hasCompare=0;
SinglyLinedNode<Character> cursor1=new SinglyLinedNode(null,singlyLinkedList.head);
SinglyLinedNode<Character> cursor2=new SinglyLinedNode(null,singlyLinkedList1.head);
for (int i = 0; i <needConpare; i++) {
Character data1 = cursor1.next.data;
Character data = cursor2.next.data;
if (!data1.equals(data)){
System.out.println("\n\n判断结果:"+testStr+"不是回文字符串");
return;
}
}
System.out.println("\n\n判断结果:"+testStr+"是回文字符串");
}
/**
* 将字符串转换为单链表
*
* @param targetStr 目标str
* @return {@link SinglyLinkedList}<{@link Character}>
*/
static SinglyLinkedList<Character> transStringToLinkedList(String targetStr) {
if (targetStr == null || targetStr.trim().equals("")) {
return null;
}
char[] chars = targetStr.toCharArray();
SinglyLinkedList<Character> characterSinglyLinkedList = new SinglyLinkedList<>();
for (Character aChar : chars) {
characterSinglyLinkedList.add(new SinglyLinedNode<>(aChar, null));
}
return characterSinglyLinkedList;
}
}