这是算法记录的第四天,包含3道链表相关的题目,主要是双指针的技巧
leetcode:19.142.160
双指针
19.删除链表的倒数第N个节点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
进阶:尝试使用一趟扫描实现
迭代
这个思路就是先迭代一遍,获取到这个链表的长度length
,然后删除第length-n
个就行(以0开始索引)
// 迭代
func removeNthFromEnd1(head *ListNode, n int) *ListNode {
//获取链表长度
length := 0
//这里不能用head直接迭代,会导致head就指向最后一个了
for tmp := head; tmp != nil; tmp = tmp.Next {
length++
}
//虚拟节点
dummyNode := &ListNode{Val: -1, Next: head}
pre := dummyNode
//遍历达到需要删除的节点
for i := 0; i < length-n; i++ {
pre = pre.Next
}
//删除next
pre.Next = pre.Next.Next
//返回head的话当只有一个节点还要删除的时候不为nil
return dummyNode.Next
}
双指针
这个和迭代类似,只是以双指针优化了计算长度。
定义一快一慢两个指针,让fast
先走n步,再让fast
和slow
一起走,直到fast
结束,这样slow
到达的就也是length-n
的位置,不过为了方便删除,slow定义在虚拟节点,这样到达的就是需要删除的前一个
// 双指针
func removeNthFromEnd2(head *ListNode, n int) *ListNode {
//虚拟头节点
dummyHead := &ListNode{-1, head}
//定义快慢指针,slow指向虚拟头节点保证了最后停止时指向的是被删除节点的前一个
fast, slow := head, dummyHead
//快指针先走n步
for i := 0; i < n; i++ {
fast = fast.Next
}
//两个同时走,直到链尾,此时slow走了len(list)-n步
for fast != nil {
fast = fast.Next
slow = slow.Next
}
//此时slow指向的是被删除节点的前一个
//删除slow的下一个节点
slow.Next = slow.Next.Next
return head
}
160.相交链表
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null
。
图示两个链表在节点 c1
开始相交**:**
题目数据 保证 整个链式结构中不存在环。
注意,函数返回结果后,链表必须 保持其原始结构 。
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点) 是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三个节点,B 中第四个节点) 在内存中指向相同的位置。
输入: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 。
双指针-思路1
先获取两个链表的长度,再将长的先走差值的长度,两指针再同时走,此时到达链尾的距离是一样的,所以会同时遇到公共节点
func getIntersectionNode(headA, headB *ListNode) *ListNode {
//定义两链长度、长度差
lengthA, lengthB, distance := 0, 0, 0
//获取A、B两个链表的长度
for tmp := headA; tmp != nil; tmp = tmp.Next {
lengthA++
}
for tmp := headB; tmp != nil; tmp = tmp.Next {
lengthB++
}
//计算两个链表距离差值,并定义好谁先谁后
var fast, slow *ListNode
if lengthB > lengthA {
distance = lengthB - lengthA
fast, slow = headB, headA
} else {
distance = lengthA - lengthB
fast, slow = headA, headB
}
//前面的都是准备工作,下面的才是关键
//快的先走distance步
for i := 0; i < distance; i++ {
fast = fast.Next
}
//再一起走,相同就说明到公共节点了
for slow != fast {
fast = fast.Next
slow = slow.Next
}
//返回公共节点
return slow
}
双指针-思路2
两个指针同时遍历AB两链,遍历完后就分别继续遍历BA两链,这样相当于两链拼接,若有公共节点的话两指针必然会在第一个公共节点处相等,关键在于两条链本身长度可能不同,但加起来长度就是相同的了,所以进行一个拼接操作,使最后的公共部分会同时到达
func getIntersectionNode2(headA, headB *ListNode) *ListNode {
//两个指针分别指向A、B链表头
p1, p2 := headA, headB
//不断迭代,若相同说明到达公共节点
for p1 != p2 {
//不是空(不是A链的尾部的空指针,就继续迭代A)
if p1 != nil {
p1 = p1.Next
} else {
//否则换成B链继续迭代
p1 = headB
}
//同理
if p2 != nil {
p2 = p2.Next
} else {
p2 = headA
}
}
//返回相遇的节点
return p1
}
142.环形链表II
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0
输出:返回索引为 0 的链表节点
解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1
输出:返回 null
解释:链表中没有环。
双指针
使用快慢指针,让fast
步长为2,slow
步长为1,这样fast
走过的距离始终是slow
的两倍。
若存在环,那么两个指针一定会在环内某处相遇,假设slow
走了n
步,fast
就是2n
,假设相遇点距离环的起点是m
,那么头节点和起点的距离就是n-m
,而正好,如果从相遇点继续沿着环走,走n-m
步也会到环的起点。所以,让相遇的时候fast/slow
中任一个和head
同时走,相遇点就是环的起点。
//快慢指针
func detectCycle(head *ListNode) *ListNode {
//都在头节点开始
fast, slow := head, head
//还得先判断fast,用例存在[]情况
for fast != nil && fast.Next != nil {
//fast走两步,slow走一步
fast = fast.Next.Next
slow = slow.Next
//相遇的情况
if fast == slow {
for fast != head {
//fast和head同时走,相遇即在环开始的地方
fast = fast.Next
head = head.Next
}
//返回环的起点
return head
}
}
//没环就返回nil
return nil
}