Ltd的算法大抄·链表II·双指针

leetcode:19.142.160

Posted by Ltd on Wednesday, March 8, 2023

这是算法记录的第四天,包含3道链表相关的题目,主要是双指针的技巧

leetcode:19.142.160

双指针

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

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

示例 1:

img

输入: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步,再让fastslow一起走,直到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.相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始相交**:** img

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

示例 1: img

输入: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 中第四个节点) 在内存中指向相同的位置。

示例 2: 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 。

双指针-思路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: img

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

示例 2:

img

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

示例 3: img

输入: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
}