快慢指针
快慢指针就是用一快一慢两个指针来遍历数据对象并解决问题,常用于解决例如2倍速来求中间指针或判断循环链表、恒定n个差值或用于寻找倒数第n个指针等问题。
1、环形链表
快慢指针的经典应用场景就是解决环形链表问题。如何判断一个链表是否为环形链表,可以使用暴力解的方法,但这不是最优解,解决该问题唯一的最优解就是环形链表。
快慢指针的原理是,快慢指针同时从链表头遍历链表,设定快指针每次前进两个节点,慢指针每次前进一个节点,如果链表存在环,那快慢指针最终会在环中循环,又由于快指针每次只比慢指针多前进一个节点,那快指针就必然能够追上慢指针。如果链表不存在环,则两指针除非遍历到链表尾,否则不会再相遇。
总结下来就是以下两种结果:
- 如果快指针到达NULL,说明链表以NULL结尾,不存在环。
- 如果快指针追上慢指针,则表示有环。
算法实现如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *slow,*fast;
slow = fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next; // 快指针每次前进两个节点,慢指针每次前进一个节点
if (slow == fast) {
struct ListNode *meet = slow;
while (meet != head) {
meet = meet->next;
head = head->next; // meet与head同时向后走一步,注意与前面快慢指针所走步数的不同
}
return meet;
}
}
return NULL;
}
2、找中间值
如果把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head)
{
struct ListNode *slow, *fast;
slow = fast = head;
while (fast && fast->next->next) {
slow = slow->next;
fast = fast->next->next; // 快指针每次前进两个节点,慢指针每次前进一个节点
}
return slow; // 快指针走到尾节点时,慢指针正好走到中间值
}
3、删除倒数第N个节点
快慢指针还可以用来解决删除链表第N个节点问题,假设快指针先走n步,之后慢指针开始遍历,并且和快指针速度一致,当快指针到达链表尾部时,慢指针刚好移动到倒数第n-1个节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *removeNthFromEnd(struct ListNode *head,int n)
{
struct ListNode *nodeTmp = malloc(sizeof(struct ListNode));
nodeTmp->val = 0;
nodeTmp->next = head;
struct ListNode *fast = head;
struct ListNode *slow = nodeTmp;
for(int i = 0; i < n; ++i) {
fast = fast->next;
}
while(fast) {
fast= fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
struct ListNode *ans = nodeTmp->next;
free(nodeTmp);
return ans;
}
快慢指针在链表类的迭代中,时间复杂度是O(n)的量级,是一种能有效控制时间复杂的算法。