算法套路五——快慢指针
算法示例LeetCode876. 链表的中间结点
给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
可以设置快慢指针,快指针每次移动两步,慢指针每次移动一步,进过数学推导,可以得出无论数组长度是奇数还是偶数,都可以保证快指针移动到末尾时慢指针移动到数组中间结点,不过要注意的是考虑到由于fast每次走两步,所以循环判断条件为fast and fast.next都不为空
class Solution:
def middleNode(self, head: ListNode) -> ListNode:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
练习LeetCode141. 环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环 ,则返回 true 。 否则,返回 false
和上题一样,设置快慢指针,当快指针不为空时,快慢指针往前走,然后比较fast与slow,若存在环,则fast一定会追上slow,此时返回true;若没有环,迟早会退出循环,返回false
func hasCycle(head *ListNode) bool {
fast,slow:=head,head
for fast!=nil&&fast.Next!=nil{
fast=fast.Next.Next
slow=slow.Next
if slow==fast{
return true
}
}
return false
}
练习LeetCode142. 环形链表 II
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
设链表共有 a+b个节点,其中 链表头部到链表入口 有 a 个节点(不计链表入口节点), 链表环 有 b 个节点,设f为fast的走的步数,s为slow走的步数,则有
fast 走的步数是slow步数的 2 倍,即 f = 2s;fast 比 slow多走了 n 个环的长度,即 f = s + nb;
以上两式相减得:f = 2nb,s = nb(即fast和slow 指针分别走了 2n,n 个 环的周长)
从head结点走到入环点需要走 a + nb步, 而slow已经走了nb,那么slow再走a步就是入环点
了。
如何知道slow刚好走了a步? 从head开始,和slow指针一起走,相遇时刚好就是a步
func detectCycle(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
if fast == slow {
for slow != head {
slow = slow.Next
head = head.Next
}
return slow
}
}
return nil
}
练习LeetCode143. 重排链表
给定一个单链表 L 的头节点 head ,单链表 L 表示为:L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
这题结合LeetCode876. 链表的中间结点与LeetCode206. 反转链表,先找到中间结点mid,并对后半段进行反转,之后双指针slow与fast不断往后移动进行连接,且注意nxt1,nxt2保留前后两端的链指针
func reorderList(head *ListNode) {
mid:=middleNode(head)
slow:=head
fast:=reverseList(mid)
for fast.Next!=nil{
nxt1:=slow.Next
nxt2:=fast.Next
slow.Next=fast
fast.Next=nxt1
slow=nxt1
fast=nxt2
}
}
func middleNode(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
func reverseList(head *ListNode) *ListNode {
var pre *ListNode=nil
cur:=head
for cur!=nil{
nxt:=cur.Next
cur.Next=pre
pre=cur
cur=nxt
}
return pre
}
练习LeetCode234. 回文链表
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
法一:快慢指针
找到中间结点,反转后半部分链表,判断是否回文,返回结果。
func isPalindrome(head *ListNode) bool {
mid:=middleNode(head)
head2:=reverseList(mid)
for head2!=nil{
if head2.Val!=head.Val{
return false
}else{
head=head.Next
head2=head2.Next
}
}
return true
}
func middleNode(head *ListNode) *ListNode {
slow, fast := head, head
for fast != nil && fast.Next != nil {
slow = slow.Next
fast = fast.Next.Next
}
return slow
}
func reverseList(head *ListNode) *ListNode {
var pre *ListNode=nil
cur:=head
for cur!=nil{
nxt:=cur.Next
cur.Next=pre
pre=cur
cur=nxt
}
return pre
}
法二:链表化为数组+相向双指针
func isPalindrome(head *ListNode) bool {
arr:=linkedToArray(head)
i,j:=0,len(arr)-1
for i<j{
if arr[i]!=arr[j]{
return false
}else{
i++
j--
}
}
return true
}
func linkedToArray(head *ListNode) []int{
arr:=[]int{}
for head!=nil{
arr=append(arr,head.Val)
head=head.Next
}
return arr
}