主要参考:
文章目录
问题:
若链表存在环,请找到入环点。
快慢指针算法思想
定义快慢指针fast和slow,起始均位于链表头部。
规定fast每次后移2步,slow后移1步;
(1)若fast遇到null节点,则表示链表无环,结束;
(2)若fast和slow再次相遇,则表示链表中有环;
当fast和slow相遇时,额外创建指针ptr,并指向链表头部,且每次后移1步,最终slow和ptr会在入环点相遇。
问题1、为什么fast和slow一定会相遇?
例如,两人绕操场跑圈,只要一直跑,快的肯定会追上慢的,即再次相遇。
可以把操场想成一个个格子组成的链表环,slow每次跳一格,fast每次跳两格,只有两人站在同一个格子上,才算相遇。如果是跳跃式的穿过,那是不算相遇的。
slow和fast之间的距离,有以下几种情况:
- 相距1格,下次相遇。
- 相距2格,下一次移动,变为相距1格。
- 其它时刻,每次环内移动,距离缩短1,直至相距2格。
通过分析可知,快慢指针一定会相遇。
问题2、fast和slow相遇时,slow指针是否绕环超过一圈?
环长为N,还是slow进环的时候,相距为n。那么一定n<N。
根据第一题可知,slow每次 前进后 距离变为n-1,当距离为0的时候也就是n-1*n。slow就走了n。
因为n<N,所以一定不超过1圈。
问题3、为什么ptr和slow相遇的节点一定是入环点?
如图所示,假设环外部分长度为a,slow指针进入环后,又走了b的距离与fast相遇。
此时,fast指针已经走完了环的n圈,因此它走过的总距离为: a+n(b+c)+b=a+(n+1)b+nc
通过问题2的解答,slow指针是不可能绕环超过一圈的,即相遇时,slow走的距离为a+b。
任意时刻,fast指针走过的距离都为slow指针的2倍 =》因此得出关系式:
a+(n+1)b +nc = 2(a+b) =》a = c+(n - 1)(b+c)
a=c+(n−1)(b+c)这个等量关系特别重要,其中c表示slow与fast指针相遇位置到入环点的距离,而(n−1)(b+c)则是 n-1圈的环长。
因此,只需要再添加一个指针ptr指向头结点,当它走完环外距离a的时候,则会与在绕圈等它的slow相遇。而相遇点恰好是入环点。
问题4、为什么fast指针每次移动2步,能不能移动3、4、5…步?
设环外长度为w,环长度为s。取一特殊值j,保证j>w且是s整数倍的最小值。将slow走了j步后的位置记为X(j),则fast走了kj步,记为X(kj),其中k为fast与slow的速度比值。
因为j>w,所以slow和fast都在环内,而且X(kj)可以看做从X(j)出发,走了(k-1)*j步,因为j是环长的整数倍,所以又回到了X(j),两者相遇。
从上面的分析可知,无论fast取任何值,两者都会相遇。即使比值K是小数2.3(如slow=10,fast=23),也只需要j乘以10,就证明了这个问题。
之所以取fast=2,是因为快指针的时间复杂度为O(n*fast),可以保证算法效率最高。
快慢指针解题模板
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null) {
return null;
}
ListNode slow = head, fast = head;
while (fast != null) {
slow = slow.next;
if (fast.next != null) {
fast = fast.next.next;
} else {
return null;
}
if (fast == slow) {
ListNode ptr = head;
while (ptr != slow) {
ptr = ptr.next;
slow = slow.next;
}
return ptr;
}
}
return null;
}
}
leetcode部分相关题目
链表:
leetcode中链表的声明
/**
* 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) {}
* };
*/
141. 环形链表
class Solution {
public:
bool hasCycle(ListNode *head) {
//快慢指针判断链表中是否有环
//边界考虑 只有1个或没有节点 不可能为环形链表
if(head==NULL || head->next==NULL){
return false;
}
ListNode * slow=head;
ListNode * fast=head;
while(fast){
if(fast->next==NULL){
return false;
}
slow=slow->next;
fast=fast->next->next;
if(slow==fast){
break;
}
}
if(fast==slow){
return true;
}
else{
return false;
}
}
};
142. 环形链表 II
142. 环形链表 II
快慢指针:
如果存在有环,那么快慢指针必然相互追上,但是追上的位置不一定是环 开始的位置。
因此需要考虑 在判断链表有环后怎么消除两个指针之间的距离差
(参考前面问题3:为什么ptr和slow相遇的节点一定是入环点)
class Solution {
public:
//快慢指针
ListNode *detectCycle2(ListNode *head) {
if(head==NULL || head->next==NULL){
return NULL;
}
ListNode *slow=head;
ListNode *fast=head;
//第一轮追及 相等时退出
while(fast){
if(fast->next==NULL){
return NULL;
}
slow=slow->next;
fast=fast->next->next;
if(slow==fast){
break;
}
}
//第二轮追及
if(slow!=fast){
return NULL;
}
else{
ListNode *pre1=head;
ListNode *pre2=slow;
while(pre1!=pre2){
pre1=pre1->next;
pre2=pre2->next;
}
return pre1;
}
}
};
哈希表:
class Solution {
public:
//哈希表
ListNode *detectCycle(ListNode *head) {
//将链表中每个节点的位置存放在哈希表中
unordered_set<ListNode *> hashmap;
ListNode *p=head;
while(p){
if(hashmap.count(p)==0){
hashmap.insert(p);
p=p->next;
}
if(hashmap.count(p)!=0){
return p;
}
}
return NULL;
}
};
234. 回文链表
class Solution {
public:
//反转链表
ListNode* reverseList(ListNode* head) {
ListNode* prev = nullptr;
ListNode* curr = head;
while (curr != nullptr) {
ListNode* nextTemp = curr->next;
curr->next = prev;
prev = curr;
curr = nextTemp;
}
return prev;
}
bool isPalindrome(ListNode* head) {
//快慢指针+反转链表
//利用快慢指针找到链表中间位置
//边界
if(head==NULL){
return false;
}
if(head->next==NULL){
return true;
}
ListNode *slow=head;
ListNode *fast=head;
while(fast!= nullptr){
//走到最后一个有效位置
if(fast->next == nullptr||fast->next->next == nullptr)
break;
slow=slow->next;
fast=fast->next->next;
}
//最后slow位置即为链表中间位置
//但是 如果直接这样还要考虑奇数偶数问题
ListNode* p1 = head;
ListNode* p2 = reverseList(slow->next);
bool result = true;
while (result && p2 != nullptr) {
if (p1->val != p2->val) {
result = false;
}
p1 = p1->next;
p2 = p2->next;
}
return result;
}
};
数组:
287. 寻找重复数
class Solution {
public:
int findDuplicate(vector<int>& nums) {
//快慢指针
//因为题目给定 只有一个重复的整数
int slow=0;
int fast=0;
while(nums[fast]!=NULL){
slow=nums[slow];
fast=nums[nums[fast]];
if(slow==fast)
break;
}
int pre1=0;
int pre2=slow;
while(pre1!=pre2){
pre1=nums[pre1];
pre2=nums[pre2];
}
return pre1;
}
};
202. 快乐数
202. 快乐数
思路(参考官方题解):
7是快乐数
116不是快乐数
问题转换为是否存在循环
(1)可以用哈希表检测循环
class Solution {
public:
int nextNum(int n){
int cur;
while(n>0){
int d=n%10;
cout<<d<<endl;
n=n/10;
cur=cur+d*d;
}
return cur;
}
bool isHappy(int n) {
unordered_map<int,int> mp;
while(n!=1 && mp.count(n)==0){
mp[n]++;
n=nextNum(n);
}
cout<<n;
return n==1;
}
};
(2)可以用快慢指针检测循环
class Solution {
public:
int nextNum(int n){
int cur;
while(n>0){
int d=n%10;
cout<<d<<endl;
n=n/10;
cur=cur+d*d;
}
return cur;
}
bool isHappy(int n) {
int fast=n;
int slow=n;
slow=nextNum(slow);//slow走一步
fast=nextNum(nextNum(fast));//fast走两步
while(fast!=1){
if(fast==slow)
break;
slow=nextNum(slow);//slow走一步
fast=nextNum(nextNum(fast));//fast走两步
}
if(fast==1)
return true;
else
return false;
}
};