快慢指针可用于判断链表中是否有环。
Floyd判圈算法(龟兔赛跑算法)
假设乌龟和兔子在链表上跑步,兔子跑得快,乌龟跑的慢,如果链表中没有环,那么兔子将会一直在乌龟前面,直到终点;反之,如果链表中存在环,那么兔子一定会和乌龟再次相遇。
我们可以设定两个指针,快指针fast指代兔子,慢指针slow指代乌龟,假设快指针一次走两步,慢指针一次走一步。
初始化时有两种方法:
1.fast = head.next , slow = head;用fast != slow当作循环条件
2.fast = slow = head,使用do-while()循环。
例题:
1.环形链表(https://leetcode-cn.com/problems/linked-list-cycle/)
给定一个链表,判断链表中是否有环。
思路:快慢指针模板题
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr) return false;
ListNode* fast;
fast = head->next;
ListNode* slow = head;
while(slow != fast){
if(fast == nullptr || fast->next == nullptr) return false;
slow = slow->next;
fast = fast->next->next;
}
return true;
}
};
2.环形链表 II (https://leetcode-cn.com/problems/linked-list-cycle-ii/)
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
思路:跟上一题相比多了一个地方,需要找到入环点。我们设以下几个变量:
a:表示头节点到入环点之间的距离
b:慢指针在圈中所走的距离
c:整圈距离减去b所走距离的剩余距离
由此可以得到快慢指针相遇时,快指针所走距离:a+n*(a+c)+b(按第一种方法,快指针所走距离需要减1,因为fast=head->next),慢指针所走距离:a+b
而快指针的速度是慢指针的两倍,故快指针所走距离也是慢指针所走距离的两倍
a+n(b+c)+b = 2(a+b) => a = (n-1)b + nc
由此我们可以得出,当慢指针从相遇得地方再走c个距离,就会走到入环点,我们另建一个指针ptr=head,当ptr == slow时,即为入环点
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
if(head == nullptr || head->next == nullptr) return nullptr;
ListNode * fast = head->next,*slow = head;
while(slow != fast){
if(fast == nullptr || fast->next == nullptr) return nullptr;
slow = slow->next;
fast = fast->next->next;
}
// cout<<"********
";
ListNode* ptr = head;
slow=slow->next; //使用方法一需要改的地方
while(ptr != slow){
ptr = ptr->next;
slow = slow->next;
}
return ptr;
}
};
3.快乐数(https://leetcode-cn.com/problems/happy-number/)
编写一个算法来判断一个数 n 是不是快乐数。
思路:通过模拟可以发现不管几位数最后都会小于243,然后结果不是归为1,就是会陷入一个循环当中。因此,我们可以使用快慢指针判断是否有环的存在
class Solution {
public:
int getNum(int n){
int sum = 0;
while(n>0){
int x = n%10;
n /= 10;
sum += (x*x);
}
return sum;
}
bool isHappy(int n) {
if(n==1) return true;
int slow = n;
int fast = getNum(n);
// cout<<fast<<endl;
while(fast!=1 && slow != fast){
slow = getNum(slow);
fast = getNum(getNum(fast));
}
return fast==1;
}
};
4.链表的中间结点(https://leetcode-cn.com/problems/middle-of-the-linked-list/)
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
思路:这是快慢指针最简单的应用场景了,当快指针到达终点时,慢指针刚好到达中点(终点中点不一样)
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
if(head->next == nullptr) return head;
do{
slow = slow->next;
fast = fast->next->next;
// cout<<slow->val<<' '<<fast->val<<endl;
}while(fast != nullptr && fast->next != nullptr);
// cout<<fast->val<<' '<<fast->next->val<<endl;
return slow;
}
};