环形链表题解

环形链表的常见题型包括:判断链表是否有环、寻找环的入口以及判断环的长度。这类问题都可以使用快慢指针法来解决。

算法的原理来自于弗洛伊德判圈法。原理解析可以看下:https://cloud.tencent.com/developer/article/1999816

这里再补充一点,很多的文章都说若链表有环,则快慢指针必定相遇,并且还类比了龟兔赛跑的例子,说是在一个操场上,a和b以不同的速度沿着相同的方向出发一定会相遇。但是现在讨论的是链表,一个是离散空间,一个是连续空间实际上是不能直接类比的。等价的问题应该是,兔子每隔1米一个脚印,乌龟每隔0.05米一个脚印,问步数相同的情况下,龟兔可不可能在同一个脚印下相遇。

快慢指针法的前提条件是快慢指针在环内必定相遇,这里实际上是可以证明(证伪)的,下面本文会给出一个简单的证明。

问题模型可以简化为下图所示。

问题一:A和B在一个环状链表内从同一个起点出发,A每次走m步,B每次走n步,链表长度为L,请问AB一定会相遇吗?

事实上是一定会相遇的。假设经过k次后AB相遇,那么一定有

m \cdot k \ \bmod L = n \cdot k \ \bmod L

问题就转换成了寻找合适的k,事实上直接让k=L这个等式就成立了,并且如果k是L的整数倍,那么这个等式一定成立,所以这个问题一定有解还不止一个。

问题二:A和B在一个环状链表内从不同起点出发,A每次走m步,B每次走n步,链表长度为L,请问AB一定会相遇吗?

假设AB距离为s,那么问题就转换成了:

(m \cdot k +s)\ \bmod L = n \cdot k \ \bmod L

m和n都是正整数,假设m-n=p,那么上式就转换成了

(p \cdot k +s)\ \bmod L = 0

p/k/s/L都是正整数的情况下,这个等式不一定有解,举个反例,

m=5, n=3, s=31, L=100,带入就变成了:

(2*k +31) \bmod100 = 0

显而易见的,无论k取什么值,都不可能让等式成立,不信的同学可以写程序验证一下。

所以在题目中,经常可以看到快指针走2步,慢指针走1步,这样差值是1,即使起点不同,两个指针也一定有交集。

下面再证明一下弗洛伊德判圈法的原理,示意图如下图

假设有2个指针,fast和slow,都从start出发。fast每次走2步,slow每次走1步。环的长度是L

第一次相遇在meet点,meet点到enter点的长度是n,start到enter的长度是m,第一次相遇时慢节点走了a圈,a大于等于0,快节点则走了b圈,b大于等于1(为什么大于等于1呢?不明白自己好好品一下)。设慢节点走过的路程为S,那快节点走过的路程就是2S,则有:

S=m+n+a \cdot L

2S=m+n+b\cdot L

 最终的目的是求出m的长度,因此需要消去S,第二个公式减去第一个公式x2得出:

m=(b-2a)L-n

这个公式说明了,起点start到环入口enter的距离是环长度的整数倍再减去一个数值,这个数值就是从环入口到两个节点第一次相遇走过的长度。

重点:如果让AB两个指针分别从start/enter出发,那么当A走到enter的时候,B实际上走的距离是(b-2a)L-n。那如果我们先让B指针从enter出发,先走n个节点,然后A再出发,两个指针每次都走一个节点,最后肯定相交于enter。先走n个节点的位置就是前面快慢指针相遇的位置。

因此在快慢指针相遇后,直接让其中一个指针重新指向头节点,然后每个指针每次都移动一个位置,快慢指针相遇的位置就是环的入口!

做题还是简单的,leetcode上经典的两个题目:

/**
 * 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 = head;
        ListNode *slow = head;
        while(fast && fast->next)
        {
            fast = fast->next->next;
            slow = slow->next;
            if(fast==slow)
                return true;
        }
        return false;
    }
};

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
  
        ListNode *fast = head;
        ListNode *slow = head;
        while(fast && fast->next)
        {
            slow = slow->next;
            // 为空说明没有环,必须先判断,不判断直接执行下一行代码会报错
            if(fast->next == NULL)
                return NULL;
            fast = fast->next->next;
            if(slow==fast)
            {
                fast=head;
                while(fast!=slow)
                {
                    fast=fast->next;
                    slow=slow->next;
                }
                return fast;
            }
        }
        return NULL;
    }
};

这是一条吃饭博客,由挨踢零声赞助

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值