接下来我分析两道带环的题和一个通过带环问题的思路牵扯到的一个问题。并带着大家一步步分析,第一道会是很简单,第二道是第一道题的加强版,将所有问题全部考虑在内,大家如果学会了这两道题的分析,那么其他的题也只是时间的问题~!!!
接下来先看问题:
首先我们先要读懂题,知道题要我们判断什么,要返回什么,如果题都不好,那么不可能做对,所以我们一定要认真读题。
所以我们要想着分开讨论,有环的时候返回什么,没环的时候返回什么。
分析:
这里我就要说一个经典的方法了,那么就是快慢指针,也就是一个指针走的快,一个指针走的慢,快指针一次走两步,慢指针一次走一步。如果快指针走着走着走到 NULL 了,那就说明不带环,如果快指针最后和慢指针相遇了,那就说明是带环的。
而且我们要想好极限条件,也就是如果链表为 NULL ,或者就一个链头呢?这时会不会出问题?这么一想其实是会的,因为 fast 走两步的时候会对 fast 解引用,但是我们知道NULL是不能解引用的,所以我们要想如何把这两个问题过滤掉。其实如果链表为NULL,或者一个链头,那么肯定是不带环的,直接返回 fault 就可以了。
根究以上分析,我们就可以写代码了。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
bool hasCycle(struct ListNode *head) {
struct ListNode* fast = head , *slow = head;//起始条件
while (fast && fast->next)//终止条件
{
slow = slow->next;//循环条件
fast = fast->next->next;
if(slow == fast)
return true;
}
return false;
}
其实这个很简单吧,只要应用快慢指针就可以轻松解决这个问题,其实博主在写这个题的时候,不小心写成了以下的形式,大家也可以看看自己可不可能出现这个错误。
其实乍一看,也没什么问题,不就是在循环里没有返回,出来的时候再返回的么。
但其实还是有问题的,因为有这个一个情况,也就是只有一个链头的时候进不去循环,所以直接跳出来,因为我们定义的就是 fast = head , slow = head ,所以这时本应该返回 false 的,却因为将这个放在外面判断语句而返回了 true ,所以大家也要注意~!!!
接下来先思考两个问题:
- 为什么 slow 走一步,fast 走两步,它们一定会在环里相遇,会不会永远追不上?
- slow 走一步,fast 走 三步、四步、走 n 步行不行?
- 求环的入口点。
如果大家想好了请看下面我的分析:
问题一:
首先肯定是会相遇的,肯定会追上的,这里用一个很简单的数学思想就可以判断,因为 slow 走一步, fast 走两步,那么可以看做 slow 不动, fast 走一步它们的步差是 1 。
所以如果有环的话,一定是可以追上并相遇的。
问题二:
这个问题我先不给结论,大家先跟着我来分析:
所以只要符合 N 是奇数 , C 是偶数,就永远追不上。
这是 fast 走三步的时候,大家如果感兴趣,可以自己去推推走四步 , 走 n 步的时候哦,其实最后得到的结论都是有可能追不上。
所以 fast 每次只能走两步,走其他的步数都有一个特定的条件会使之追不上。
那么接下来我们带着问题三来看下一题:
这道题就是如果有环就要找环的入口点返回入口点,如果没环就返回NULL。
接下来我们继续分析:
大家想想,这么列式子对么?如果不对,那么是哪里出了问题呢?
其实当有问题的时候,我们只要想想极限的情况就能分析出来了,那么什么时候的极限的时候呢?
我们根据 L = N * C - X ,所以我们只要将一个指针放在开头,将另一个指针放在 X ,也就是它们相遇处,当他们相遇,也就是在 开头的指针 入环的时候相遇,那么 在X 位置处的指针与开头的指针相遇的位置就是入环的第一个节点。
这里就解决了问题,我们将所有问题分析清楚了,接下来就是写代码了:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode* slow = head , *fast = head;
while (fast && fast->next)
{
slow = slow->next;
fast = fast->next->next;
if(slow == fast)
{
struct ListNode* meet = fast;
while (meet != head)
{
head = head->next;
meet = meet->next;
}
if(head == meet)
return meet;
}
}
return NULL;
}
讲到这里这两道题就讲完了,其实最后一个问题可以不推公式,换一个思路去写。
将相遇点断开后,就转换成了相交的问题。
接下来我给大家分析分析这道题~!
分析完了,我们也就可以写代码了,注意可能会出现的问题,在开头、循环里、结尾进行判断可能出现问题的地方,就完成了~!
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if(headA == NULL || headB == NULL)
return NULL;
struct ListNode* curA = headA,*curB = headB;
int lenA = 0, lenB = 0;//每个的步长都少1,但是步差不变。
while(curA)//如果不相交最后还是返回NULL
{
curA = curA->next;
lenA++;
}
while(curB)//如果不相交最后还是返回NULL
{
curB = curB->next;
lenB++;
}
//长的先走步差,然后一起走。
struct ListNode* fast = headA , *slow = headB;//假设长的是A
if(lenA < lenB)//调整
{
fast = headB;
slow = headA;
}
int gap = abs(lenA - lenB);//步差
while(gap--)
{
fast = fast->next;
}
while(fast != slow)
{
fast = fast->next;
slow = slow->next;
}
return fast;
}
以上就是三道LeetCode的题,大家如果感觉自己刷起来有些难度,可以经常来看我的文章,我只要刷到经典的题,我就会分享给大家~!!!
大家如果哪里没听懂,或者我讲的哪里有问题,可以在评论区里告诉我~!我会回答大家的问题给予反馈的。
谢谢大家耐心看完~!