上篇主要总结分析了双指针中的左右指针,本篇来唠一唠双指针中的另外一种用法,快慢指针。
没错就是字面意思,两个指针一个快一些,一个慢一些,那它能解决我们什么问题呢?
说类比龟兔赛跑其实并不是很适合,环问题可以理解为小学数学的追及问题,如果跑道是环形的,那么两个运动员一快一慢,跑的快的总会在某一个点追上跑的慢的,同样走的快的指针在存在环的链表上总会有追上走的慢的指针的时候,如果两个指针能相遇那么链表必定存在环。两个链表上的指针从同一节点出发,其中一个指针前进速度是另一个指针的两倍。利用快慢指针可以用来解决某些算法问题,比如:
一、计算链表的中点:
快慢指针从头节点出发,每轮迭代中,快指针向前移动两个节点,慢指针向前移动一个节点,最终当快指针到达终点的时候,慢指针刚好在中间的节点。(大人到终点,小孩在中间 )
如下图(PS:嫌弃我的人可以,别嫌弃我的图Ծ‸Ծ):
ListNode slow,fast;
slow = fast = head;
while (fast != nu1l && fast.next != nu1l) {
fast = fast. next.next;
slow = slow. next;
}
// slow就在中间位置
return slow;
二、判断链表是否有环:
如果链表中存在环,则在链表上不断前进的指针会一直在环里绕圈子,且不能知道链表是否有环。使用快慢指针,当链表中存在环时,两个指针最终会在环中相遇。(环中相遇)
private static class LinkedNode {
int value;
LinkedNode next;
}
boolean detectCycle(ListNode head){
ListNode fast,slow;
fast = slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next; //2的步长
slow = slow.next; //1的步长
if(fast == slow){
return true;//相遇说明成环
}
}
return false;
}
三、判断链表中环的起点:求B点
当我们判断出链表中存在环,并且知道了两个指针相遇的节点,我们可以让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。
解释如下:
此时slow走了k步 fast 走了2k步,相遇点距离圈的切点BC等于L2,
那么如果要判断B点的位置就只需要让其中一个结点回到A起点,然后两者同速进行,相遇就是切点。
BC = m,AB = k - m, C转一圈到B = k - m
private static class LinkedNode {
int value;
LinkedNode next;
}
ListNode detectCycle(ListNode head){
ListNode fast,slow;
fast = slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next; //2的步长
slow = slow.next; //1的步长
if(fast == slow) break;
}
//接下来是找圈的切点的位置
slow = head; //先让一个结点回到起点
while(fast != slow){
fast = fast.next;
slow = slow.next; //以相等步长前进
}
return slow;
}
四、求链表中环的长度:
只要相遇后一个不动,另一个前进直到相遇算一下走了多少步就好了。
private static class LinkedNode {
int value;
LinkedNode next;
}
int detectCycle(ListNode head){
ListNode fast,slow;
fast = slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next; //2的步长
slow = slow.next; //1的步长
if(fast == slow){
break;
}
}
if(fast == null || fast.next == null){
//判断无环
return null;
}
int count = 0;
slow = slow.next;
while(fast != slow){
count++;
}
return count;
}
五、求链表倒数第k个元素:
先让其中一个指针向前走k步,接着两个指针以同样的速度一起向前进,直到前面的指针走到尽头了,则后面的指针即为倒数第k个元素。(这个严格来说应该叫先后指针而非快慢指针)
private static class LinkedNode {
int value;
LinkedNode next;
}
ListNode detectCycle(ListNode head, int k){
ListNode fast, slow;
fast = slow = head;
while(k-- > 0){
//先走k步
fast = fast.next;
}
while(fast.next != null){
//fast 和slow 相隔k个节点 然后以相同步速走完
fast = fast.next;
slow = slow.next;
}
return slow;
}
六、LeetCode实战解题
快速导航:142. 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:
你是否可以使用 O(1) 空间解决此题?
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:返回索引为 1 的链表节点
解释:链表中有一个环,其尾部连接到第二个节点。
示例 2:
输入:head = [1,2], pos = 0 输出:返回索引为 0 的链表节点 解释:链表中有一个环,其尾部连接到第一个节点。
示例 3:
输入:head = [1], pos = -1 输出:返回 null 解释:链表中没有环。
解题思路:
还要啥解题思路,见上快慢指针的第三大点模板代码。
代码:
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
//快慢指针判断环
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
break;
}
}
if(fast == null || fast.next == null){
//判断无环
return null;
}
fast = head;
while(fast != slow){
//获取环的切入点
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
【遇事不决,可问春风!】