题目链接:leetcode24-两两交换链表中的节点, leetcode19-删除链表倒数第N个节点, leetcode160-链表相交, leetcode142-环形链表II
两两交换链表中的节点
基础题没有什么技巧
解题思路见代码注释
时间复杂度: O(n)
空间复杂度: O(1)
Go
func swapPairs(head *ListNode) *ListNode {if head == nil || head.Next == nil {return head}virtualHead := &ListNode{Next: head}// 指针preNode, curNode := virtualHead, headvar curNodeNext *ListNodefor curNode != nil && curNode.Next != nil {curNodeNext = curNode.Next// 初始:cur指向1,curNext指向2// prevNode ---> 1----> 2 -------> 3// cur curNext// curNode与curNodeNext进行节点交换// prevNode ---> 1 2 -------> 3// |_______________^// cur curNextcurNode.Next = curNodeNext.Next// _______// V |// prevNode ---> 1 2 3// |_______________^// cur curNextcurNodeNext.Next = curNode// prevNode ---> 2 -----> 1 -----> 3// curNext curpreNode.Next = curNodeNext// 指针移动// 2 -----> 1 -----> 3// preNode curpreNode = curNodecurNode = curNode.Next}return virtualHead.Next
}
删除链表倒数第N个节点
思路
节点数范围:1 <= sz <= 30
n范围: 1 <= n <= sz (即n一定时小于节点的数量)
思路:使用快慢指针,slow, fast
- 创建一个dummy head
- 从dummy开始, 初始slow,fast都指向dummy, fast先移动n个节点
- slow和fast同时移动,当fast.Next==nil时,slow所处位置即为倒数第n个节点的前一个节点
example:
dummyH ---> 1 ----> 2 ----> 3 ---> 4
假如n=2, 则要删除倒数第2个节点,即节点3
初始s和f都指向dummyH:dummyH ---> 1 ----> 2 ----> 3 ---> 4s ff先移动n个节点:dummyH ---> 1 ----> 2 ----> 3 ---> 4s fs和f同时移动:dummyH ---> 1 ----> 2 ----> 3 ---> 4s f
即当f.Next == nil时,s刚好处于倒数第n个节点的前一个节点,此时只需要执行s.Next = s.Next.Next,就删除了目标节点
时间复杂度: O(n)
空间复杂度: O(1)
Go
func removeNthFromEnd(head *ListNode, n int) *ListNode {if head == nil {return head}dummyH := &ListNode{Next: head}s, f := dummyH, dummyH// f先移动n个节点for i := 0; i < n; i++ {f = f.Next}for f.Next != nil {s = s.Nextf = f.Next}// 此时s所处的位置为倒数第n个节点的前一个节点s.Next = s.Next.Nextreturn dummyH.Next
}
链表相交
思路
方法1:
使用map将A链表节点存储,遍历B链表时判断当前节点是否已经在map中存在,如果存在当前节点就是相交节点.
此方法时间复杂度O(m+n),m和n分别是链表A,B的长度, 空间复杂度O(m)
方法2:
将A B补成等长的链表,如下:
A: a1—>a2—>c1—>c2—>c3—>b1—>b2—>b3—>c1—>c2—>c3
B: b1—>b2—>b3—>c1—>c2—>c3—>a1—>a2—>c1—>c2—>c3
可以看到,是在c1处相交
原理:
假设A链表中,不相交的节点数量有a,相交的节点数量有c,总长度为a+c
假设B链表中,不相交的节点数量有b,相交的节点数量有c,总长度为b+c
使用pa, pb两指针同时遍历A链表和B链表
假如a==b, 遍历链表的两指针会同时到达相交的节点
假如a!=b,pa遍历A链表之后遍历B链表,pb遍历B链表之后遍历A链表
此时对于A链表来说总长度为: (a+c) + (b+c) —> (a+c+b) + c
对于B链表来说总长度为: (b+c) + (a+c) —> (b+c+a) + c
即,pa和pb都遍历了(a+b+c)个节点然后同时到达了相交的节点
时间复杂度O(m+n),m和n分别是链表A,B的长度
空间复杂度O(1)
Go
func getIntersectionNode(headA, headB *ListNode) *ListNode {// 两个链表都不为nil时才会相交if headA == nil || headB == nil {return nil}pa, pb := headA, headB// 如果A和B没有相交的点,最终pa和pb都会为nil, 然后退出for pa != pb {if pa == nil {// A已经遍历完,开始遍历Bpa = headB} else {pa = pa.Next}if pb == nil {// B已经遍历完开始遍历Apb = headA} else {pb = pb.Next}}return pa
}
环形链表II
思路
- 判断链表是否有环
- 找出环的入口(返回入口节点所在的索引,索引从0开始)
推导过程
初始状态:
存在双指针,slow、fast初始都指向链表的表头,slow的步长为1,fast的步长为2。
如果链表存在环,则slow与fast 一定会在环内相交 ,因为fast相对于slow的步长为1,即fast在一步一步的接近slow。
则有:
怎么求环的入口?
设链表head到环入口的距离为x, 环入口到slow和fast相交点的距离为y,相交点到环入口的距离为z,则有:
slow和fast同时从链表头开始移动到相交分别移以下距离:
slow:x+y
fast: x+y+n(y+z)
(fast与slow相交之前可能已经在环中转了n圈,一圈距离为y+z)
因为fast的步长为slow的2倍则有:
2(x+y)= x+y+n(y+z)
--> x+y = n(y+z)
--> x = n(y+z)-y
--> x = (n-1)(y+z) + z
由x = (n-1)(y+z) + z
可以得知,x的长度等距离z加上n-1圈的环的距离((y+z)为一圈环的距离,且(n-1) >= 0)则有:
假如存在两个指针p1, p2, p1指向链表头部向着环入口出发,p2指向slow与fast的相交点向着环入口出发,p1和p2同时开始移动,
则p1与p2的相交点即为环入口。 (即p2从相交点移动到环入口(距离z)后可能会转(n-1)圈环)
Go
func detectCycle(head *ListNode) *ListNode {slow, fast := head, head// 如果存在环,fast.Next不可能为nilfor fast != nil && fast.Next != nil {// slow的步长为1// fast的步长为2slow = slow.Nextfast = fast.Next.Next// 当slow与fast相等时,说明在环内相交了if slow == fast {// 找出环的入口p1 := headp2 := slow// p1从链表头部开始移动,p2从相交点开始移动,两者相交的点即为环的入口for p1 != p2 {p1, p2 = p1.Next, p2.Next}return p1}}return nil
}