题目链接: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, head
var curNodeNext *ListNode
for 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 curNext
curNode.Next = curNodeNext.Next
// _______
// V |
// prevNode ---> 1 2 3
// |_______________^
// cur curNext
curNodeNext.Next = curNode
// prevNode ---> 2 -----> 1 -----> 3
// curNext cur
preNode.Next = curNodeNext
// 指针移动
// 2 -----> 1 -----> 3
// preNode cur
preNode = curNode
curNode = 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 ---> 4
s f
f先移动n个节点:
dummyH ---> 1 ----> 2 ----> 3 ---> 4
s f
s和f同时移动:
dummyH ---> 1 ----> 2 ----> 3 ---> 4
s 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.Next
f = f.Next
}
// 此时s所处的位置为倒数第n个节点的前一个节点
s.Next = s.Next.Next
return 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已经遍历完,开始遍历B
pa = headB
} else {
pa = pa.Next
}
if pb == nil {
// B已经遍历完开始遍历A
pb = 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不可能为nil
for fast != nil && fast.Next != nil {
// slow的步长为1
// fast的步长为2
slow = slow.Next
fast = fast.Next.Next
// 当slow与fast相等时,说明在环内相交了
if slow == fast {
// 找出环的入口
p1 := head
p2 := slow
// p1从链表头部开始移动,p2从相交点开始移动,两者相交的点即为环的入口
for p1 != p2 {
p1, p2 = p1.Next, p2.Next
}
return p1
}
}
return nil
}