目录
何为快慢指针
eg1.判断单链表是否成环 leetcode.141
eg2.在eg1的基础上寻找环的入口点 剑指 Offer II 022
eg3.寻找链表中的倒数第k个节点 剑指 Offer 22
eg4.删除链表的中间节点 leetcode.2095
eg5.判断链表是否相交并返回相交节点 leetcode.160
eg.5拾遗
快慢指针为双指针算法的一个具体范例。其核心为声明两个指针,在指针遍历的过程治中风快慢指针所走的步长是不一样的,一般来说慢指针步长常设为1,快指针步长常设为2。
设置慢指针slow和快指针fast,slow的步长为1,fast的步长为2,同时开始遍历链表。
若链表成环这两个指针则必然会相遇,此时return true
反之fast则会遍历到null,此时return false
bool hasCycle(struct ListNode *head)
{
struct ListNode *fast;
struct ListNode *slow;
fast = head;
slow = head;
if(head == NULL) { return false; }
while(1)
{
if(fast->next == NULL || fast->next->next == NULL)
{
return false;
}
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
return true;
}
}
}
先判断单链表是否成环,若不成环则无需进行下面操作。
当快慢指针相遇时,不再有快慢指针的区分,将其中一个指针置于链表表头,另一个指针位置不变,两个继续遍历,当这两个指针再次相遇时,相遇点即为环的入口点。
其中具体数学过程参见https://zhuanlan.zhihu.com/p/361049436
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *fast;
struct ListNode *slow;
fast = head;
slow = head;
//判断链表中是否有环并且找到快慢指针相遇点
//代码与eg.1相同
if(head == NULL) { return NULL; }
while(1)
{
if(fast->next == NULL || fast->next->next == NULL)
{
return NULL;
}
fast = fast->next->next;
slow = slow->next;
if(fast == slow)
{
break;
}
}
//寻找环的入口点
fast = head;
while(1)
{
if(fast == slow)
{
return fast;
}
fast = fast->next;//此时不再设快慢指针,两个指针的步长均为1
slow = slow->next;
}
}
设置慢指针slow和快指针fast,fast比slow快k = 1步,同时开始遍历链表。
当fast遍历到链表尾时slow正好遍历到链表中的倒数第k个节点。
struct ListNode* getKthFromEnd(struct ListNode* head, int k)
{
struct ListNode *fast;
struct ListNode *slow;
fast = head;
slow = head;
//使fast比slow快k - 1步
for(int i = 1; i < k; ++i)
{
fast = fast->next;
}
while(fast->next != NULL)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
设置慢指针slow和快指针fast,slow的步长为1,fast的步长为2,同时开始遍历链表。
需按照链表长度分奇偶进行讨论:
若为偶数:当fast遍历到链表尾时slow正好遍历到链表的中点。
若为若为奇数:当fast遍历到链表尾时slow正好遍历到链表的中点前的第一个节点。
struct ListNode* deleteMiddle(struct ListNode* head)
{
struct ListNode *fast;
struct ListNode *slow;
struct ListNode *slowformer;
fast = head;
slow = head;
slowformer = NULL;
if(head->next == NULL || head == NULL) { return NULL; }
while(1)
{
if(fast->next == NULL)//偶数情况
{
slowformer->next = slowformer->next->next;
return head;
}
if(fast->next->next == NULL)//奇数情况
{
slow->next = slow->next->next;
return head;
}
fast = fast->next->next;
slowformer = slow;
slow = slow->next;
}
}
设置遍历链表A的指针和遍历链表B的指针,将这两个指针分别遍历至链表尾,若此时两个指针指向的内存相同则这两个链表相交。
找出两个链表长度的差值,并判断哪个链表的长度大。下文中根据长度的不同将这两个链表称为长链表和短链表。
将这两个指针重新初始化至链表头。
将遍历长链表的指针向后遍历两个链表长度的差值的步数。遍历短链表的指针位置不变。
两个指针同时开始遍历,此二指针第一次相遇的节点即为两个单链表的交点。
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB)
{
struct ListNode *A;
struct ListNode *B;
A = headA;
B = headB;
int i = 0;
bool temp;
while(A->next != NULL && B->next != NULL)
{
A = A->next;
B = B->next;
}
while(A->next != NULL)
{
A = A->next;
++i;//i为两个链表长度的差值
temp = false;//temp表示A的长度大于B
}
while(B->next != NULL)
{
B = B->next;
++i;//i为两个链表长度的差值
temp = true;//temp表示B的长度大于A
}
if(A != B) { return NULL; }//判断是否相交
A = headA;
B = headB;
if(temp)//将两个指针之后的单链表长度相同
{
for(int j = 0; j < i; ++j)
{
B = B->next;
}
}
else
{
for(int j = 0; j < i; ++j)
{
A = A->next;
}
}
while(A != B)//若两个指针相遇,则此节点为两个单链表的交点
{
A = A->next;
B = B->next;
}
return A;
}
此题目中规定两个单链表不成环,下面讨论成环的情况。
若一个单链表成环,但另一个不成环,则这两个单链表必不相交。
若两个单链表均成环,可根据eg2找出环的入口点,判断这两个入口点是否指向同一块内存来判断是否相交。或者可以根据遍历两个链表的指针是否相遇来判断是否相交。