双指针技巧之快慢指针
双指针技巧分为两类:快慢指针
和左右指针
,前者主要解决链表中的问题,后者用来解决数组和字符串问题,下面将详细介绍快慢指针.
快慢指针的常用算法
快慢指针初始化时一般指向链表的头结点head
,快指针fast
在前,一次走两步,慢指针slow
在后,一次走一步。
快慢指针典例1:判断链表中是否有环
我们很容易会想到一个方法:通过判断指针的下一结点是否为空,来判断链表中是否含环
bool hasCycle(LinkList* head){
Linklist* p = head;
while(p){
p = p->next;
}
return false; //如果p为空,则链表有环
}
但是,如果链表中有环时,while会陷入死循环,因为环形链表中没有NULL
指针作为尾部结点,所以单指针的方法是行不通的,这就需要我们用双指针中的快慢指针来解决。
快慢指针中,快指针一次走两步,慢指针一次走一步。如果链表不含环,快指针最终会指向NULL
,说明链表不含环;如果含有环,快指针最终会追上慢指针,两者相遇,说明链表含环。
bool hasCycle(ListNode* head){
ListNode* fast, * slow; //定义快、慢指针
fast = slow = head; //初始化时快、慢指针指向头结点
while(fast && fast->next){ //循环条件:fast不为NULL 且 fast->next不为NULL(主要是为了下句代码fast->next->next不出现空指针异常)
fast = fast->next->next; //快指针一次走两步
slow = slow->next; //慢指针一次走一步
if(fast == slow) return true; //快指针追上慢指针,两者相遇,说明链表含环。
}
return false; //快指针指向```NULL```或者快指针的下一结点为```NULL```,说明链表不含环
}
LeetCode 141. 环形链表
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode* fast, * slow;
fast = slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow) return true;//若快慢指针相遇,则表示链表中有环
}
return false;//快指针为空或者快指针的下一结点指向空,则表示链表中没有环
}
};
快慢指针典例1进阶:已知链表中含有环,返回这个环的起始位置
ListNode *detectCycle(ListNode *head){
ListNode * fast, * slow;
fast = slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow) break; //快慢指针相遇
}
if(!fast || !(fast->next)) return NULL;
slow = head; //让快指针或者慢指针指向head
while(slow != fast){
fast = fast->next; //两个指针以同样的速度向前
slow = slow->next;
}
return slow; //当再次相遇时,slow或fast的位置就是环的入口
}
LeetCode 142. 环形链表 II
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head){
ListNode * fast, * slow;
fast = slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
if(fast == slow) break; //快慢指针相遇
}
if(!fast || !(fast->next)) return NULL;
slow = head; //让快指针或者慢指针指向head
while(slow != fast){
fast = fast->next; //两个指针以同样的速度向前
slow = slow->next;
}
return slow; //当再次相遇时,slow或fast的位置就是环的入口
}
};
快慢指针典例2:寻找无环单链表的中点
我们很容易想到一个方法:先遍历一次链表统计出链表的结点数n,然后再遍历一次链表找到第n/2个结点,但是这种做法不是很高效,下面介绍快慢指针的方法。
快指针一次走两步,慢指针一次走一步,当快指针到达链表尽头时,慢指针的位置就是中间结点的位置
ListNode* middleNode(ListNode* head){
ListNode* fast, * slow;
fast = slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
}
return slow; //快指针到达尽头时,慢指针的位置就是中间结点的位置
}
LeetCode 876. 链表的中间结点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* fast, * slow;
fast = slow = head;
while(fast && fast->next){
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
};
快慢指针典例2进阶:寻找单链表的倒是第k个元素
思路:先让快指针走k步,然后快慢指针同时以相同的速度前进,当快指针到链表末尾NULL时,慢指针所指的位置就是倒数第k个元素。
ListNode* getKthFromEnd(ListNode* head, int k){ //假设k不会超过链表长度 具体问题具体分析
ListNode* fast, * slow;
fast = slow = head;
while(k--){
fast = fast->next; //快指针先走k步
}
while(fast){
fast = fast->next; // 快慢指针同时以相同的速度前进
slow = slow->next;
}
return slow; //最后慢指针所指的位置就是倒数第k个元素
}
剑指 Offer 22. 链表中倒数第k个节点
类似:面试题 02.02. 返回倒数第 k 个节点
类似:ACwing 33. 链表中倒数第k个节点
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) { //此题k不会超过链表长度
ListNode* fast, * slow;
fast = slow = head;
while(k--){
fast = fast->next;
}
while(fast){
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
19. 删除链表的倒数第 N 个结点
小技巧:在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的next
指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) { //依题意链表中至少有一个结点, n是有效值
ListNode* dummy = new ListNode(); // 定义一个哑结点dummy 避免对头结点进行特殊判断
dummy->next = head;
ListNode* fast, * slow, * p;
fast = slow = dummy;
int k = n + 1; //删除倒数第n个元素,则找到倒数第n+1个元素
while(k--){ //快指针先走n+1步
fast = fast->next;
}
while(fast){ //快慢指针一起走
fast = fast->next;
slow = slow->next;
}
//慢指针就停在倒数第n+1个结点
p = slow->next;
slow->next = p->next;
delete p; //释放删除结点
return dummy->next; //注意不是返回dummy
}
};
本文作者:keyongkang
本文链接:https://www.cnblogs.com/keyongkang/p/15057287.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步