面试被拷打复盘 之 链表判环(快慢指针)(leetcode)

链表判环?快指针走3,4,5…行不行?为什么不行?和什么有关?什么情况下行,什么时候不行?

昨天面试被拷打了…一紧张没推出来…
答:
典中典之快慢指针?(一个走一步,一个走两步)

不是所有的步数都能判断的,需要考虑的因素很多很多…
(慢指针入环时两指针的距离D,两个指针的步数差值gap,走的次数n,环的长度len)
分析:
这里设起点s,终点t,他们相差D,即s + D = t;(t没超过环长)

则节点关系式为:s + n % len = (t + step * n) % len(1)
其中:
n:走了多少次;
Len:环的长度;
Step:快指针每次走多少步
只有满足这个关系式的时候才能相遇;

变形一下,把t拆开,s去掉:n % len = (D + step * n) % len(2)
所以,当step是2时,变形一下:(D + n) % len = 0(3)
所以我们n取klen – D就行,且一定能取到;
一般的,我们可以把(2)变形为:(D + (step - 1)
n) % len = 0(4)

所以n要取:n = (k*len - D)/ (step - 1),n为整数,才能成立(快慢指针相遇)。
但是不是所有情况都能取到的,比如:step = 3,len = 4,D = 1的时候n无法为整数,所以快慢指针永远无法相遇,因此无法判断链表是否有环。

所以到这里我们知道了,快慢指针(一个一步,一个两部)一定能判断链表是否有环;

接下来我们来看一下怎么找到环的入口?

例题
我们思考一下,既然快指针比慢指针多走1步,假设从链表头到环入口长度为L,那么当慢指针到环入口时,慢指针走了L步,快指针走了2L步,而这2L步中有L步是如环前走的,有L步是如环后走的,我们不妨让L%len,那么这个L%len是不是就是快慢指针的初始差值?对吧,也就是上面的D;

由上面可知,我们n取k*len – D就有解,所以我们n不妨取len – L%len,此时也就是说我慢指针走了len – L%len步,也就是相比慢指针的初始位置s,相遇节点是s + (len – L%len),中间相差了len – L%len步,此时有没有发现,len是环的长度,那么我们是不是从相遇节点再走L步就是**(s + (len – L%len)+ L)%len**,然后把 %len全部放进去,最后剩了s节点,所以说我们从相遇节点再走L步之后就到达了s节点,也就是链表的入口处,这样我们就找到了链表的入口!

那么问题就来了,我不知道链表的入口啊,也就不知道链表头到入口的长度L,那我怎么走?

首先通过上面的分析,我们可以得到相遇节点,且从相遇节点走L到达入口,而L又是链表头到入口的长度;所以我们可以再搞一个指针指向链表头,然后两个指针(链表头指针,相遇节点指针)一起走,直到两个指针相遇,那么是不是就都走了L步,对吧。也就是找到了链表的入口处!

下面是参考代码:

/**
 * 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 * f = head;    //快指针
        ListNode * s = head;    //慢指针
        ListNode * ans = nullptr;
        int num = 0;    //相遇次数
        while(1)
        {
            if(f == nullptr || f->next == nullptr) break; //没有环
            if(s == f) num ++;  //相遇
            
            if(num >= 2)    //在环内相遇
            {
                ans = s;
                break;
            }

            s = s->next;
            f = f->next->next;
        }

        if(ans == nullptr)
        {
            return ans;
        }

        ListNode * p = head;
        while(p != ans)
        {
            p = p->next;
            ans = ans->next;
        }

        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Java中,使用快慢指针(也称为龟兔赛跑法)是常见的判断链表是否有环的方法。这种方法基于两个指针,一个每次移动一个节点,另一个每次移动两个节点。如果链表中有环,那么指针最终会追上指针;如果没有环,指针会先到达链表尾部。 下面是基本的实现骤: 1. 初始化两个指针:`slow`(指针)和`fast`(指针),分别指向链表的头节点。 2. 指针遍历:如果链表不为空,循环执行以下操作: a. `slow`向前移动一(`slow.next`)。 b. `fast`向前移动两(`fast.next.next`)。 3. 判断环的存在:如果`fast`指针在某次迭代中到达了`null`,说明链表没有环,因为指针过的距离是指针的两倍,如果链表长度为偶数,指针应该在链表末尾找到指针,如果奇数,则会先到尾部再回环,不会追上。如果`fast`始终不为`null`,且与`slow`相遇(它们都指向同一个节点),那么链表中存在环。 下面是伪代码形式的实现: ```java public boolean hasCycle(ListNode head) { ListNode slow = head; ListNode fast = head; // 如果链表为空或只有一个节点,不存在环 if (head == null || head.next == null) { return false; } // 快慢指针开始遍历 while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; // 如果指针先到末尾,则链表无环 if (fast == null) { return false; } // 如果快慢指针相遇,说明链表有环 if (slow == fast) { return true; } } return false; // 如果没有提前结束循环,说明链表无环 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值