这两天在复习链表,我一想,这链表这么简单的东西还有什么复习的,简单过一遍不就行了
然而马上打脸,有些题目我居然还写不出来(乐
理论基础
先来点你肯定知道的东西,简单过一遍
是什么
如图所示,链表是一种链式结构,以最简单的单链表为例:
1 2 3 4 type ListNode struct { Val int Next *ListNode }
也就是头节点指向下一个节点,然后下一个节点再指向下一个节点,如果是尾节点就指向 NULL
(或者说是 Golang 里的 nil
)
基本操作
删除节点
让前面节点认为它的后面是后面的节点
删除的节点让 GC 自动回收掉即可
添加节点
让新节点指向后面节点,让前面节点指向新节点
链表的几种变体
有头链表
减少对头节点的特殊处理
双向链表
可以方便地找到前驱节点
对于一个有序链表,双向链表的按值查询效率要比单链表高一些。因为我们可以记录上次查找的位置p,每一次查询时,根据要查找的值与p的大小关系,决定是往前还是往后查找,所以平均只需要查找一半的数据。
循环链表
能解决约瑟夫问题
有头双向循环链表
buff 叠满,请见 【有头双向循环链表之美,这个数据结构简单又优雅,学会了不亏】
链表 vs 数组
插入删除
随机访问
数组
O ( n ) O(n) O ( n )
O ( 1 ) O(1) O ( 1 )
链表
O ( 1 ) O(1) O ( 1 )
O ( n ) O(n) O ( n )
必知必会的题目
实现一个链表,这里就以带头单链表为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 type MyLinkedList struct { head *ListNode size int } type ListNode struct { val int next *ListNode } func Constructor () MyLinkedList { dummyHead := &ListNode{} return MyLinkedList{ head: dummyHead, size: 0 , } } func (this *MyLinkedList) Get(index int ) int { if index < 0 || index >= this.size { return -1 } cur := this.head.next for i := 0 ; i < index; i++ { cur = cur.next } return cur.val } func (this *MyLinkedList) AddAtHead(val int ) { newNode := &ListNode{val: val} newNode.next = this.head.next this.head.next = newNode this.size++ } func (this *MyLinkedList) AddAtTail(val int ) { newNode := &ListNode{val: val} cur := this.head for cur.next != nil { cur = cur.next } cur.next = newNode this.size++ } func (this *MyLinkedList) AddAtIndex(index int , val int ) { if index < 0 || index > this.size { return } if index == this.size { this.AddAtTail(val) return } cur := this.head for i := 0 ; i < index; i++ { cur = cur.next } newNode := &ListNode{val: val} newNode.next = cur.next cur.next = newNode this.size++ } func (this *MyLinkedList) DeleteAtIndex(index int ) { if index < 0 || index >= this.size { return } cur := this.head for i := 0 ; i < index; i++ { cur = cur.next } cur.next = cur.next.next this.size-- }
字面意思,翻转一个链表
操作过程如下:
初始化当前节点为头节点
暂存下一个节点
将当前节点指向前一个节点
前驱节点后移
当前节点后移
重复 1-4,只到当前节点为 NULL
,输出前驱结点为新的头结点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 func reverseList (head *ListNode) *ListNode { var prev *ListNode = nil var next *ListNode = nil var curr *ListNode = head for curr != nil { next = curr.Next curr.Next = prev prev = curr curr = next } return prev }
我一开始也不是很懂,所以我画了个图一步一步来,图画出来我就懂了
类似地,还有一个递归的写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func reverseList (head *ListNode) *ListNode { if head == nil || head.Next == nil { return head } p := reverseList(head.Next) head.Next.Next = head head.Next = nil return p }
使用快慢指针( i
走一步, j
走两步)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func middleNode (head *ListNode) *ListNode { var fast *ListNode = head var slow *ListNode = head for fast != nil && fast.Next != nil { fast = fast.Next.Next slow = slow.Next } return slow }
第一版以为不能修改他给出的链表,每次都 new 一个节点,结果 TLE 了,好尴尬
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func mergeTwoLists (list1 *ListNode, list2 *ListNode) *ListNode { dummy := &ListNode{} curr := dummy for list1 != nil && list2 != nil { if list1.Val > list2.Val { curr.Next = list2 list2 = list2.Next curr = curr.Next } else { curr.Next = list1 list1 = list1.Next curr = curr.Next } } if list1 != nil { curr.Next = list1 } if list2 != nil { curr.Next = list2 } return dummy.Next }
用快慢指针找到中点,然后反转后半部分,从开始和中间向后进行比较
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 func isPalindrome (head *ListNode) bool { var slow *ListNode = head var fast *ListNode = head for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next } if fast != nil { slow = slow.Next } slow = reverseList(slow) for slow != nil { if slow.Val != head.Val { return false } slow = slow.Next head = head.Next } return true }
还是双指针,i
j
初始为 head
,然后 j
先走 N
步,然后一起往后走
这个东西有点坑,一开始没弄哨兵节点,删除的操作总是有问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func removeNthFromEnd (head *ListNode, n int ) *ListNode { dummy := &ListNode{Next: head} slow, fast := dummy, dummy for i := 0 ; i < n; i++ { fast = fast.Next } for fast.Next != nil { slow = slow.Next fast = fast.Next } slow.Next = slow.Next.Next return dummy.Next }
判断一个链表中有没有环
还是快慢指针,如果快指针能追上慢指针就肯定有环了
1 2 3 4 5 6 7 8 9 10 11 12 13 func hasCycle (head *ListNode) bool { fast := head slow := head for fast != nil && fast.Next != nil { slow = slow.Next fast = fast.Next.Next if fast == slow { return true } } return false }
当然你也可以用更直白的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func hasCycle (head *ListNode) bool { if head == nil || head.Next == nil { return false } for head != nil { if head.Val == 123512151 { return true } else { head.Val = 123512151 } head = head.Next } return false }
这个题目需要在判断出有环后能找到环的起点
推理过程请见 代码随想录 👀
反正结论就是:当快慢指针相遇时,让其中一个回到头结点,然后同速前进,再次相遇即为环开始的点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 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 { fast = head for fast != slow { fast = fast.Next slow = slow.Next } return fast } } return nil }
理论上你也可以使用更直白的方法,但是题目要求不能修改链表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 func getIntersectionNode (headA, headB *ListNode) *ListNode { lengthA := getLength(headA) lengthB := getLength(headB) var fast, slow *ListNode step := 0 if lengthA > lengthB { step = lengthA - lengthB fast, slow = headA, headB } else { step = lengthB - lengthA fast, slow = headB, headA } for i := 0 ; i < step; i++ { fast = fast.Next } for fast != slow { fast = fast.Next slow = slow.Next } return fast } func getLength (head *ListNode) (length int ) { for ; head != nil ; head = head.Next { length++ } return }