数据结构-单链表

链表(list)

与数组相似,链表也是一种线性数据结构。如下图所示:

img

由图可知,链表中的每个元素实际上是一个单独的对象,而所有对象都通过每个元素中的引用字段链接在一起。与数组不同,我们无法在常量时间内访问单链表中的随机元素。 如果我们想要获得第 i 个元素,我们必须从头结点逐个遍历。 我们按索引访问元素平均要花费 O(N) 时间,其中 N 是链表的长度。

链表有两种类型:单链表双链表。上图为单链表,下图为双链表

img

链表的添加与删除元素功能十分方便

设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:valnextval 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

示例:

MyLinkedList linkedList = new MyLinkedList();
linkedList.addAtHead(1);
linkedList.addAtTail(3);
linkedList.addAtIndex(1,2);   //链表变为1-> 2-> 3
linkedList.get(1);            //返回2
linkedList.deleteAtIndex(1);  //现在链表是1-> 3
linkedList.get(1);            //返回3

提示:

  • 所有val值都在 [1, 1000] 之内。
  • 操作次数将在 [1, 1000] 之内。
  • 请不要使用内置的 链码 库。
  • 哨兵节点在树和链表中被广泛用作伪头、伪尾等,通常不保存任何数据。我们将使用伪头来简化我们简化插入和删除。

解:

/*
	构造函数时设定哨兵节点,头节点的地址为head.next
	新节点插入最后一个节点之后,只需要把链表最后一个节点的指针指向新节点,新节点的指针再指向None即可
	节点插入在X、Y之间,只要将X节点的指针指向新节点,新节点指针指向Y即可
*/

// 定义链表结构
type MyLinkedList struct {
	Val	int	// 链表上数据
	Next *MyLinkedList // 指向下一个节点的指针
}


// 构造函数
// 在这里设定哨兵节点
func Constructor() MyLinkedList {
	return MyLinkedList{
		Val:  0,
		Next: nil,
	}
}

// 获取链表中第 `index` 个节点的值。如果索引无效,则返回`-1`。
// 链表的头节点(哨兵节点) 和 第一个存储节点不是一个节点,头节点的地址为head.Next
func (head *MyLinkedList) Get(index int) int {
	// p 初始化指向列表第二个节点
	p := head.Next

	// 只要指针指向的下一个节点p非空,便一直循环
	// 每循环一次index-1,指针前进一位,直到index=0,
	// 此时到达要获取的节点位置,返回val
	for p != nil {
		if index == 0 {
			return p.Val
		}
		p = p.Next
		index -= 1
	}
	return -1
}


// 在链表的第一个元素之前添加一个值为 `val` 的节点。插入后,新节点将成为链表的第一个节点。
func (head *MyLinkedList) AddAtHead(val int)  {
	// 要插入的第一个节点的值
	node := &MyLinkedList{Val:val}
	// 新节点的指针指向链表原来的第一个节点
	node.Next = head.Next
	// 此时head.next已经又向前移动了一位,然后将链表头指针指向新节点,新节点成为第一个存储节点
	head.Next = node
}


// 将值为 `val` 的节点追加到链表的最后一个元素。
// 新节点插入最后一个节点之后,只需要把链表最后一个节点的指针指向新节点,新节点的指针再指向None即可
func (head *MyLinkedList) AddAtTail(val int)  {
	// p 初始化 为指向 链表第一个节点的指针
	p := head.Next
	// 循环结束后,p.next指向最后一个节点的下一个节点
	for p.Next != nil {
		p = p.Next
	}
	// 赋值
	p.Next = &MyLinkedList{Val: val, Next: nil}
}


// 在链表中的第 `index` 个节点之前添加值为 `val` 的节点。
// 如果 `index` 等于链表的长度,则该节点将附加到链表的末尾。
// 如果 `index` 大于链表长度,则不会插入节点。
// 如果`index`小于0,则在头部插入节点。
// 节点在X、Y之间只要将X节点的指针指向新节点,新节点指针指向Y即可
func (head *MyLinkedList) AddAtIndex(index int, val int)  {

	// p 初始化 为指向 链表伪头节点的指针
	p := head
	// 插入值地址
	node := &MyLinkedList{Val: val, Next: nil}
	// 若当前节点非空(即没有到链表尾部),便一直循环
	for p != nil {
		if index == 0 {
			node.Next = p.Next
			p.Next = node
			break
		}
		index--

		p = p.Next
	}

}


// 如果索引 `index` 有效,则删除链表中的第 `index` 个节点
func (head *MyLinkedList) DeleteAtIndex(index int)  {
	// p 初始化 为指向 链表伪头节点的指针
	p := head
	
	for p.Next != nil {
		if index == 0 {
			p.Next = p.Next.Next
			break
		}
		index -= 1
		p = p.Next
	}
}

Golang中list包

在Go语言中,链表使用 container/list 包来实现,内部的实现原理是双链表,在实际中应用并不多。

package main

import (
	"container/list"
	"fmt"
)

func main() {
   
   // 创建一个链表对象
   l := list.New()
   
   // 尾部添加元素
   l.PushBack("hello")
   
   // 头部添加元素
   l.PushFront("boy")
   
   // 尾部添加后保存元素索引
   element := l.PushBack("see")
   
   // 在"see"之后添加"you"
   l.InsertAfter("you", element)
   
   // 在"see"之前添加"again"
   l.InsertBefore("again", element)
   
   // 删除"see"
   l.Remove(element)
   
   fmt.Println(l)
}

输出:

&{{0xc000076390 0xc0000763f0 <nil> <nil>} 4}

这里具体的0x实际记录的是对象的地址,可能会变动,但是最后的"4"不会变动,其表示当前的链表对象里面有4个元素。

那我们要怎么输出呢,这里就需要采用循环遍历的形式:

	//输出list的值
	for e := l.Front(); e != nil; e = e.Next() {
		fmt.Println(e.Value)
	}

下面是所有常见list包中函数:

// 返回该元素的下一个元素,如果没有下一个元素则返回nil
func (e *Element) Next() *Element  

// 返回该元素的前一个元素,如果没有前一个元素则返回nil。
func (e *Element) Prev() *Element

type List 
// 返回一个初始化的list
func New() *List

// 获取list l的最后一个元素
func (l *List) Back() *Element 

// 获取list l的第一个元素
func (l *List) Front() *Element

// list l初始化或者清除list l
func (l *List) Init() *List  

// 在list l中元素mark之后插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
func (l *List) InsertAfter(v interface{}, mark *Element) *Element  

// 在list l中元素mark之前插入一个值为v的元素,并返回该元素,如果mark不是list中元素,则list不改变。
func (l *List) InsertBefore(v interface{}, mark *Element) *Element

// 获取list l的长度
func (l *List) Len() int 

// 将元素e移动到元素mark之后,如果元素e或者mark不属于list l,或者e==mark,则list l不改变。
func (l *List) MoveAfter(e, mark *Element)  

// 将元素e移动到元素mark之前,如果元素e或者mark不属于list l,或者e==mark,则list l不改变。
func (l *List) MoveBefore(e, mark *Element)

// 将元素e移动到list l的末尾,如果e不属于list l,则list不改变。
func (l *List) MoveToBack(e *Element)

// 将元素e移动到list l的首部,如果e不属于list l,则list不改变。
func (l *List) MoveToFront(e *Element)

// 在list l的末尾插入值为v的元素,并返回该元素。
func (l *List) PushBack(v interface{}) *Element

// 在list l的尾部插入另外一个list,其中l和other可以相等。
func (l *List) PushBackList(other *List)

// 在list l的首部插入值为v的元素,并返回该元素。
func (l *List) PushFront(v interface{}) *Element

// 在list l的首部插入另外一个list,其中l和other可以相等。
func (l *List) PushFrontList(other *List)

// 如果元素e属于list l,将其从list中删除,并返回元素e的值。
func (l *List) Remove(e *Element) interface{}

双指针在链表中的使用

让我们从一个经典问题开始:给定一个链表,判断链表中是否有环。

想象一下,有两个速度不同的跑步者。如果他们在直路上行驶,快跑者将首先到达目的地。但是,如果它们在圆形跑道上跑步,那么快跑者如果继续跑步就会追上慢跑者。

这正是我们在链表中使用两个速度不同的指针时会遇到的情况:

  1. 如果没有环,快指针将停在链表的末尾。
  2. 如果有环,快指针最终将与慢指针相遇。

所以剩下的问题是:这两个指针的适当速度应该是多少?

一个安全的选择是每次移动慢指针一步,而移动快指针两步。每一次迭代,快速指针将额外移动一步。如果环的长度为 M,经过 M 次迭代后,快指针肯定会多绕环一周,并赶上慢指针。下面我们用练习题来进一步理解这种算法。


环形链表

给定一个链表,判断链表中是否有环

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。

img

示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。

img

示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

img

解:

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func hasCycle(head *ListNode) bool {
    
/*
    双指针法
    快慢指针
*/
    
	if head == nil {								
		return false
	}
	fast := head
	for fast != nil && fast.Next != nil {
		fast = fast.Next.Next
		head = head.Next								
		if fast == head {
			return true
		}
	}
	return false
}

环形链表 Ⅱ

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos-1,则在该链表中没有环。

说明:不允许修改给定的链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。

img

示例 2:

输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。

img

示例 3:

输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。

img

解:

用到了数学知识

详细解答1

详细解答2

png

解:

func detectCycle(head *ListNode) *ListNode {
/*
    双指针法
    首先判断是否为环形链表,如上题
    然后采用运用数学公式推导找规律,详细看上图
*/
    
	fast := head
	slow := head
	for fast != nil && fast.Next != nil {
		slow = slow.Next
		fast = fast.Next.Next
		if fast == slow {
			for fast != head {
				fast = fast.Next
				head = head.Next
			}
			return head
		}
	}
	return nil
}

// 改进版:
func detectCycle(head *ListNode) *ListNode {
    slow = head
    fast = head
    // 空列表
    for {
        if fast==nil||fast.Next==nil{
            return nil
        }

        slow = slow.Next
        fast = fast.Next.Next
        // 相交则跳出循环,相比上面降低了时间复杂度
        if slow == fast{
            break
        }
    }

    fast = head
    for fast != slow{
        slow = slow.Next
        fast = fast.Next
    }
    return slow
}

相交链表

编写一个程序,找到两个单链表相交的起始节点。

如下面的两个链表

img

在节点 c1 开始相交。

示例 1:

img

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

img

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:

img

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:

  • 如果两个链表没有交点,返回 null.
  • 在返回结果后,两个链表仍须保持原有的结构。
  • 可假定整个链表结构中没有循环。
  • 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。

解:

// 暴力破解法
func getIntersectionNode(headA, headB *ListNode) *ListNode {
	if headA == nil || headB == nil {
		return nil
	}
	p1 := headA
	p2 := headB
	for p1 != nil {
		for p2 != nil {
			if p1 == p2 {
				return p1
			}
			p2 = p2.Next
		}
		p1 = p1.Next
		p2 = headB // 关键所在
	}
	return nil
}


// 双指针法
func getIntersectionNode(headA, headB *ListNode) *ListNode {
    
/*
    两个链表长度分别为a和b,那么a+b = b+a
    因此,两个指针分别遍历这两个链表,在第一次遍历到链表a尾部的时候,指向链表b头部继续遍历,这样会抵消长度差。
    如果链表有相交,那么会在中途相等,返回相交节点(画图更为明显);
    如果链表不相交,那么最后会 nil == nil,返回 nil;
*/
    
	p1 := headA
	p2 := headB
	for p1 != p2 {
		if p1 == nil {
			p1 = headB
		} else {
			p1 = p1.Next
		}

		if p2 == nil {
			p2 = headA
		} else {
			p2 = p2.Next
		}
	}
	return p1
}

删除链表的倒数第N个节点

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.

说明:

给定的 n 保证是有效的。

解:

func removeNthFromEnd(head *ListNode, n int) *ListNode {

/*
		使用快慢两个指针,两个指针之间有n+1个结点,那么当前面的指针指向列表末尾时,
		慢指针指向倒数第n+1个结点(即被删除结点的前一个结点)
*/

	// 定义哨兵节点
	p := &ListNode{}
	p.Next = head

	// 快慢节点都指向哨兵节点
	fast := p
	slow := p

	// 快指针到达距离慢指针n+1的节点
	for i:=0;i<=n;i++ {
		fast = fast.Next
	}
	// 之后快指针与慢指针同时开始移动
	// 当快指针到达链表尾部时,慢指针指向要删除节点的前一个节点
	for fast != nil {
		fast = fast.Next
		slow = slow.Next
	}
	// 删除倒数第n个节点
	slow.Next = slow.Next.Next
	// 返回头节点
	return p.Next
}



// 进阶版:
func removeNthFromEnd(head *ListNode, n int) *ListNode {

/*
    只需对链表进行一次遍历,快慢指针相邻
*/

	// 哨兵节点
	p := &ListNode{}
	p.Next = head

	// 快慢指针
	var slow *ListNode
	fast := p

	i := 1
	for head != nil {
		// 此时快指针距离head指针n,当head到达nil时,fast即指向倒数第n个节点
		if i >= n {
			slow = fast
			fast = fast.Next
		}
		head = head.Next
		i++
	}
	// 当上面循环结束后,head指向nil,fast指向所要删除的节点
	slow.Next = slow.Next.Next
	return p.Next
}

删除链表节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。

返回删除后的链表的头节点。

示例 1:

输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:

输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

解:

func deleteNode(head *ListNode, val int) *ListNode {
    
/*
    方法同上题(在这里繁琐了)
*/
    
    p := &ListNode{}
    p.Next = head

    fast := p
    var slow *ListNode

    for fast.Next != nil {
        slow = fast
        fast = fast.Next
        if fast.Val == val {
            slow.Next = slow.Next.Next
            break
        }
    }
    return p.Next
}

// 简易版
func deleteNode(head *ListNode, val int) *ListNode {
    p := &ListNode{}
    p.Next = head

    cur := p

    for cur.Next != nil {
        if cur.Next.Val == val {
            cur.Next = cur.Next.Next
            break
        } else {
            cur = cur.Next
        }
    }
    return p.Next
}

删除链表中的节点(头脑风暴)

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。

现有一个链表 -- head = [4,5,1,9],它可以表示为:

img

示例 1:

输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.

示例 2:

输入: head = [4,5,1,9], node = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.

说明:

链表至少包含两个节点。
链表中所有节点的值都是唯一的。
给定的节点为非末尾节点并且一定是链表中的一个有效节点。
不要从你的函数中返回任何结果。

解:

func deleteNode(node *ListNode) {
	
/*
    如何让自己在世界上消失,但又不死?
    			—— 将自己完全变成另一个人,再杀了那个人就行了。
*/
    
    node.Val = node.Next.Val
    node.Next = node.Next.Next

}

经典问题 ---> 反转链表问题

让我们从一个经典问题开始:

反转一个单链表。

一种解决方案是按原始顺序迭代结点,并将它们逐个移动到列表的头部。似乎很难理解。我们先用一个例子来说明我们的算法。


让我们看一个例子:

img

请记住,黑色结点 23 是原始的头结点。

  1. 首先,我们将黑色结点的下一个结点(即结点 6)移动到列表的头部:

img

  1. 然后,我们将黑色结点的下一个结点(即结点 15)移动到列表的头部:

img

  1. 黑色结点的下一个结点现在是空。因此,我们停止这一过程并返回新的头结点 15。

注意:

在该算法中,每个结点只移动一次

因此,时间复杂度为 O(N),其中 N 是链表的长度。我们只使用常量级的额外空间,所以空间复杂度为 O(1)。

反转链表

这个题是基础中的基础:反转一个单链表

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL

解:

func reverseList(head *ListNode) *ListNode {

/*	
	迭代法
    当前节点指向老的表头,然后把当前节点修改为新的表头
    为了保证迭代正常,使用node变量缓存head.Next节点
*/

    // 空链表情况
    if head == nil {
        return nil
    }

    var preHead *ListNode // 老表头
    curHead := head // 当前表头
    
    for curHead != nil {
        
        tmp := curHead.Next   // 暂时缓存新表头
        
        curHead.Next = preHead // 当前表头指向preHead(老表头),在这里转换指针方向
        
        // 老表头 和 当前表头 都前进一位
        preHead = curHead // 老表头成为当前表头
        curHead = tmp 
    }

    return preHead
}

递归法相对难理解,这里上一张leetcode上图片,我觉得十分传神:

总结一下就是,初始化当前表头老表头前面一位,迭代开始后,若当前表头不指向nil,则:

  • 当前表头指向老表头,当前表头、老表头向前移动;

  • 当前表头指向老表头,当前表头、老表头向前移动;

  • 当前表头指向老表头,当前表头、老表头向前移动;

  • 当前表头指向老表头,当前表头、老表头向前移动.......

直到当前表头指向nil,证明迭代完毕,链表完全反转。

img

移出链表元素

删除链表中等于给定值 *val* 的所有节点。

示例:

输入: 1->2->6->3->4->5->6, val = 6
输出: 1->2->3->4->5

解:

func deleteNode(head *ListNode, val int) *ListNode {
 
    p := &ListNode{}
    p.Next = head

    cur := p

    for cur.Next != nil {
        if cur.Next.Val == val {
            cur.Next = cur.Next.Next
        } else {
            cur = cur.Next
        }
    }
    return p.Next
}

奇偶链表

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL

示例 2:

输入: 2->1->3->5->6->4->7->NULL 
输出: 2->3->6->7->1->5->4->NULL

说明:

  • 应当保持奇数节点和偶数节点的相对顺序。
  • 链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

解:

func oddEvenList(head *ListNode) *ListNode {
    
/*
    结点odd作为奇数链的头 结点even作为偶数链的头,此时记录偶数链的头evenHead

	从第3个点开始遍历,依次轮流附在奇、偶链的后面

	遍历完后,即偶链的尾为空时,奇数链的尾连向偶链的头evenHead,返回奇数链的头
*/
    
	// 空链表情况
	if head == nil {
		return nil
	}

	odd := head	// 奇数在第一位
	even := head.Next // 偶数在第二位
	evenHead := even // 记录偶数头

	for even != nil && even.Next != nil {

		odd.Next = even.Next // 奇数下一位等于偶数的下一位,即下一个奇数
		odd = odd.Next // 奇数指针指到下一奇数

		even.Next = odd.Next // 偶数的下一位等于奇数的下一位,即下一个偶数
		even = even.Next // 偶数的指针指到下一个偶数
	}
	odd.Next = evenHead	// 奇偶相接

	return head
}

参考下图:image.png

回文链表

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2
输出: false

示例 2:

输入: 1->2->2->1
输出: true

解:

func isPalindrome(head *ListNode) bool { 
/*
	使用快慢指针寻找链表中点,然后反转后半部分,再分别从开头和中点处遍历比较
*/
	
	// 空链表情况
	if head == nil {
		return true
	}

	// 1. 快慢指针寻找链表中点
	slow, fast := head, head

	for fast != nil && fast.Next != nil {
		fast = fast.Next.Next // 快指针一次走两步
		slow = slow.Next      // 慢指针一次走一步
	}

	// 2. 从中点开始反转链表后半部分,见这一部分第一个习题 -- 反转链表
	var pre, cur *ListNode = nil, slow

	for cur != nil {
		next := cur.Next // 先记录下下一个节点,不然一会就没了
		cur.Next = pre   // 当前节点指向上一个节点
		pre = cur        // 指针后移
		cur = next
	}
	// 3. 分别从开头和中点处遍历比较,pre此时指向原链表的链尾,即反抓后链表的链首
	mid := pre
	for mid != nil {
		if head.Val != mid.Val { // 比较每一个元素,但凡有一个不同,则不是回文链表
			return false
		}
		mid = mid.Next // 指针后移
		head = head.Next
	}
	return true
}
posted @ 2020-06-10 00:09  侠奢  阅读(289)  评论(0)    收藏  举报