结构与算法的理论知识到一段落,学了知识要落地,接下来用Golang来实现。

本文实现链表的回文字符串问题

如字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?(如level,noon)

那么单链表怎么存储字符串?

package main

import "fmt"

//***如字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?(如level,noon)***//

//那么单链表怎么存储字符串?
type StrNode struct {
	str  string
	next *StrNode //这个表示指向下一个结点
}

//给链表插入一个结点,在单链表的最后加入
func InsertStrNode(head *StrNode, newStrNode *StrNode) {
	//1.先找到最后一个结点
	//2.创建一个辅助结点
	temp := head
	for {
		//3.如果temp的next指向空,那就是最后一个结点
		if temp.next == nil {
			break
		}
		temp = temp.next //4.让temp不断指向下一个结点
	}
	//5.将newStrNode加入到链表的最后
	temp.next = newStrNode
	//fmt.Println("这就连上了")
}

//显示链表的所有结点信息
func ListStrNode(head *StrNode) {
	//1.创建一个辅助结点
	temp := head
	//2.判度该链表是不是空
	if temp.next == nil {
		fmt.Println("空链表,无法显示")
		return
	}

	//3.遍历这个链表
	for {
		fmt.Printf("[%s]=>", temp.str)
		//4.判断结点是否到链表结尾
		if temp.next == nil {
			fmt.Println("")
			break
		}
		temp = temp.next //5.指向下一个结点

	}
}

func main() {

	//a := 812565218

	//1.先创建一个头结点
	head := &StrNode{
		str: "8",
		//这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
	}

	//2.创建新的StrNode
	str1 := &StrNode{
		str: "1",
	}
	str2 := &StrNode{
		str: "2",
	}
	str3 := &StrNode{
		str: "5",
	}
	str4 := &StrNode{
		str: "6",
	}
	str5 := &StrNode{
		str: "5",
	}
	str6 := &StrNode{
		str: "2",
	}
	str7 := &StrNode{
		str: "1",
	}
	str8 := &StrNode{
		str: "8",
	}

	//3.加入
	InsertStrNode(head, str1)
	InsertStrNode(head, str2)
	InsertStrNode(head, str3)
	InsertStrNode(head, str4)
	InsertStrNode(head, str5)
	InsertStrNode(head, str6)
	InsertStrNode(head, str7)
	InsertStrNode(head, str8)

	//4.显示
	ListStrNode(head)
}
通过上面的代码,就把字符串存到了链表上

那么怎么判断是否为回文串?

先用数组存结点的值,再前后对比
package main

import "fmt"

//***如字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?(如level,noon)***//

//那么单链表怎么存储字符串?
type StrNode struct {
	str  string
	next *StrNode //这个表示指向下一个结点
}

//给链表插入一个结点,在单链表的最后加入
func InsertStrNode(head *StrNode, newStrNode *StrNode) {
	//1.先找到最后一个结点
	//2.创建一个辅助结点
	temp := head
	for {
		//3.如果temp的next指向空,那就是最后一个结点
		if temp.next == nil {
			break
		}
		temp = temp.next //4.让temp不断指向下一个结点
	}
	//5.将newStrNode加入到链表的最后
	temp.next = newStrNode
	//fmt.Println("这就连上了")
}

//显示链表的所有结点信息
func ListStrNode(head *StrNode) {
	//1.创建一个辅助结点
	temp := head
	//2.判度该链表是不是空
	if temp.next == nil {
		fmt.Println("空链表,无法显示")
		return
	}

	//3.遍历这个链表
	for {
		fmt.Printf("[%s]=>", temp.str)
		//4.判断结点是否到链表结尾
		if temp.next == nil {
			fmt.Println("")
			break
		}
		temp = temp.next //5.指向下一个结点

	}
}

// 用数组存结点的值,前后对比
func IsPalindrome(head *StrNode) bool {
	// 空链表,算回文
	if head == nil || head.next == nil {
		return true
	}
	//1.用数组存节点的值,将链表的值一一追加到数组
	var data []string
	for temp := head; temp != nil; temp = temp.next {
		data = append(data, temp.str)
	}
	//2.前后对比。数组为偶数个时只有i<j的情况,奇数个时有i=j的情况
	for i, j := 0, len(data)-1; i <= j; {
		if data[i] != data[j] {
			return false
		}
		i++
		j--

	}

	return true
}

func main() {

	//a := 812565218

	//1.先创建一个头结点
	head := &StrNode{
		str: "8",
		//这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
	}

	//2.创建新的StrNode
	str1 := &StrNode{
		str: "1",
	}
	str2 := &StrNode{
		str: "2",
	}
	str3 := &StrNode{
		str: "5",
	}
	str4 := &StrNode{
		str: "6",
	}
	str5 := &StrNode{
		str: "5",
	}
	str6 := &StrNode{
		str: "2",
	}
	str7 := &StrNode{
		str: "1",
	}
	str8 := &StrNode{
		str: "8",
	}

	//3.加入
	InsertStrNode(head, str1)
	InsertStrNode(head, str2)
	InsertStrNode(head, str3)
	InsertStrNode(head, str4)
	InsertStrNode(head, str5)
	InsertStrNode(head, str6)
	InsertStrNode(head, str7)
	InsertStrNode(head, str8)

	//4.显示
	ListStrNode(head)

	//5.判断
	res := IsPalindrome(head)
	fmt.Println(res)
}
正常情况
改动其中一个的值,则判断不是

这段代码的时间复杂度为O(n),空间复杂度也为O(n)。

那么有优化方案吗?

找到链表中间节点,将前半部分转置,再从中间向左右遍历对比。
使其空间复杂度降为O(1)。
package main

import "fmt"

//***如字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?(如level,noon)***//

//那么单链表怎么存储字符串?
type StrNode struct {
	str  string
	next *StrNode //这个表示指向下一个结点
}

//给链表插入一个结点,在单链表的最后加入
func InsertStrNode(head *StrNode, newStrNode *StrNode) {
	//1.先找到最后一个结点
	//2.创建一个辅助结点
	temp := head
	for {
		//3.如果temp的next指向空,那就是最后一个结点
		if temp.next == nil {
			break
		}
		temp = temp.next //4.让temp不断指向下一个结点
	}
	//5.将newStrNode加入到链表的最后
	temp.next = newStrNode
	//fmt.Println("这就连上了")
}

//显示链表的所有结点信息
func ListStrNode(head *StrNode) {
	//1.创建一个辅助结点
	temp := head
	//2.判度该链表是不是空
	if temp.next == nil {
		fmt.Println("空链表,无法显示")
		return
	}

	//3.遍历这个链表
	for {
		fmt.Printf("[%s]=>", temp.str)
		//4.判断结点是否到链表结尾
		if temp.next == nil {
			fmt.Println("")
			break
		}
		temp = temp.next //5.指向下一个结点

	}
}

// 找到链表中间节点,将前半部分转置,再从中间向左右遍历对比
func IsPalindrome(head *StrNode) bool {
	if head == nil || head.next == nil {
		return true
	}
	res := true

	//1.链表长度
	len := 0
	for temp := head; temp != nil; temp = temp.next {
		len++
	}

	//2.将前半部分反转
	step := len / 2
	var prev *StrNode
	temp := head
	for i := 1; i <= step; i++ {
		temp.next, prev, temp = prev, temp, temp.next
	}
	mid := temp

	var left, right *StrNode = prev, nil
	if len%2 == 0 {
		//长度为偶数
		right = mid
	} else {
		right = mid.next
	}

	//3.从中间向左右两边遍历对比
	for left != nil && right != nil {
		if left.str != right.str {
			//值不相等,不是回文链表
			res = false
			break
		}
		left = left.next
		right = right.next
	}

	//4.将前半部分反转的链表进行复原
	temp = prev
	prev = mid
	for temp != nil {
		temp.next, prev, temp = prev, temp, temp.next
	}

	return res
}

func main() {

	//a := 812565218

	//1.先创建一个头结点
	head := &StrNode{
		str: "8",
		//这里没法定义next,next要指向谁,要通过下一个结点的主动加入链表来确定,这里先空着
	}

	//2.创建新的StrNode
	str1 := &StrNode{
		str: "1",
	}
	str2 := &StrNode{
		str: "2",
	}
	str3 := &StrNode{
		str: "5",
	}
	str5 := &StrNode{
		str: "5",
	}
	str6 := &StrNode{
		str: "2",
	}
	str7 := &StrNode{
		str: "1",
	}
	str8 := &StrNode{
		str: "8",
	}

	//3.加入
	InsertStrNode(head, str1)
	InsertStrNode(head, str2)
	InsertStrNode(head, str3)
	InsertStrNode(head, str5)
	InsertStrNode(head, str6)
	InsertStrNode(head, str7)
	InsertStrNode(head, str8)

	//4.显示
	ListStrNode(head)

	//5.判断
	res := IsPalindrome(head)
	fmt.Println(res)
}
正常情况
改动其中一个的值,则判断不是