一、什么是双指针?
在链表中,通常设置两个指针,这两个指针每次走的长度不同,会对链表的交点等问题提供一个便捷的解决方案。
这两个指针分别称为快指针(pFast)和慢指针(pSlow)。
二、应用场景
2.1 寻找链表的中间节点
题目链接 给你单链表的头结点 head,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
(1) 解决思路
定义两个指针pFast
和pSlow
,pFast
一次走两步,pFast
一次走一步。当pFast
走完整个链表时,pSlow
恰好走至中间节点的位置。
(2) 解决代码
struct ListNode* middleNode(struct ListNode* head)
{
struct ListNode* slow = head, *fast = head;
while((fast != NULL) && (fast->next != NULL))
{
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
2.2 判断两个链表是否有相交节点
题目链接 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
(1) 解决思路
分别求出两个链表的长度,记为L1
和L2
。让长的链表走差距步(即|L1 - L2|
步),随后再一起走,第一个相同的地址点就是两个链表的交点。
(2) 解决代码
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode* tailA = headA;
struct ListNode* tailB = headB;
int lenA = 1, lenB = 1;
while(tailA->next)
{
tailA = tailA->next;
++lenA;
}
while(tailB->next)
{
tailB = tailB->next;
++lenB;
}
if(tailA != tailB)
{
return NULL;
}
int gap = abs(lenA-lenB);
struct ListNode* longList = headA;
struct ListNode* shortList = headB;
if(lenA < lenB)
{
longList = headB;
shortList = headA;
}
while(gap--)
{
longList = longList->next;
}
while(longList != shortList)
{
longList = longList->next;
shortList = shortList->next;
}
return longList;
}
2.3 判断链表是否有环。
题目链接 给你一个链表的头节点 head ,判断链表中是否有环。
- 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意pos不作为参数进行传递 。仅仅是为了标识链表的实际情况。如果链表中存在环,则返回true;否则,返回false。
(1) 解决思路
采用快慢指针来解决此问题。慢指针一次走一步,快指针一次走两步,两个指针均从链表的起始位置开始。如果链表有环,则两个指针一定会相遇;否者快指针会先走到链表的尾部。
(2) 解决代码
bool hasCycle(struct ListNode *head)
{
struct ListNode *slow = head, *fast = head;
while((fast != NULL) && (fast->next != NULL))
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
return true;
}
}
return false;
}
(3) 扩展问题
(3-1) 快慢两个指针一定会相遇?
- 一定会。快指针会先进环,慢指针会后进环。假设慢指针进环时,慢指针和快指针之间的距离为N,慢指针进环以后快指针开始追击;慢指针每走一步,快指针走两步,它们之间的距离便会缩小1。距离变换的形式为N ->(N-1) -> (N-2) -> … -> 2 -> 1 ->0(相遇)
(3-2) 快指针每次走2、3、4……步还可以相遇吗?
- 不一定。若快指针一次走偶数步,一定会相遇;若一次走奇数步,则不会相遇。
2.4 寻找链表的第一个入环节点
添加链接描述 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
- 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。不允许修改 链表。
(1) 解决思路
首先判断链表是否有环,采用上题的方式进行判断。若有环,记录快慢指针相遇的节点。让一个指针从链表起始位置开始走,另一个指针从相遇位置开始走,每次只走一步。两指针的相遇点便是链表入环的第一个节点。
(2) 解决代码
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode* slow = head, *fast = head;
while(fast && fast->next)
{
slow = slow->next;
fast = slow->next->next;
if(slow == fast)
{
struct ListNode* meet = slow;
while(head != meet)
{
head = head->next;
meet = meet->next;
}
return meet;
}
}
return NULL;
}
—— writing by Pan Qifan(潘琦藩) ——