单链表的环(二)

一、如何确定环的第1个节点

  快慢指针(F-S)都从链表头出发,最终在环形轨道上相遇,从而确定有环。此时,慢指针S原地不动,快指针F回到链表头,然后快指针降低速度(跟慢指针一样每次走1步),快慢指针同时往前走,当它们再次相遇的时候,正好在环的第1个节点。

二、为什么再次相遇在环的第1个节点?

  为什么F和S再次相遇的地方是环的第1个节点?需要推导。二者相遇在环上,这个问题本质上是模运算的问题。模运算的特点是,溢出重复,但本质上又有所不同。比如,这周的星期一和下周的星期一,就不一样;凌晨1点和中午1点在时钟上位于一个位置,但它们不一样。所以F和S相遇了,但是它们走过的距离是不一样的。

  我们可以把环无限展开,变成数轴。先举一个例子,环形链表为0->1->2->[3->4->5->6->7->8->3],无限展开为0->1->2->[3->4->5->6->7->8]->[9/3->10/4->11/5->12/6->13/7->14/8]->[15/3/9->...]->...;即0,1,2在环外,3是环的起点,环的长度为6,所以3/9/15/...是同一点,从3开始,所有相差为6的倍数的那些数都是同一个点。

  归纳一下,把链表看成数轴,第1个节点为数轴的原点,在环之前有x个节点,坐标为0,1,...,x-1;环上有r个点,坐标依次为[x,x+1,...,x+r-1],[x+r,x+r+1,...,x+2r-1],...。当快慢指针在环上相遇时,它们的坐标是不同的,因为快指针走得快,所以它的坐标比慢指针要大。另一方面,它们在环上的位置重叠(相遇),那么,它们的坐标差值为r的整倍数。另外,对于任意两个点m和n,当m≥x且n≥x(即m和n都在环上)时,如果|m-n|=kr,那么m和n在环上相遇。所以,从这里可以看出,相遇的等价条件是,坐标差值为r的整倍数。

  回到算法上来,F和S都从坐标0开始往前移动,前面讨论过,它们一定会在环上相遇。而相遇的含义是,它们的坐标差值为r的整倍数。假设S走了n步与F在环上相遇,那么,S的坐标为n,F的坐标为2n,它们的差值为2n-n=n=kr(因为是r整倍数,假设为k倍)。假设在环的前面有x个节点,即坐标x是环上的第1个节点,在环之前的坐标为0,1,...,x-1,那么必然有n≥x(即S一定走到了环上然后与F相遇)。而且,如果给出具体的x和r的数字,这里可以计算出F和S第1次相遇在哪个坐标,根据条件n=kr≥x,找到满足该条件的最小整数k,那么kr就是第1次相遇的坐标。

  第1次相遇之后,F回到坐标0点,S保持在坐标n点,然后它们以相同的速度1前进,F前进x步,来到环的第1个点坐标为x的位置,而S前进x步来到坐标n+x位置,它们的坐标差为(n+x)-x=n=kr,所以它们在环上相遇,因为x是环上的第1个点,所以它们相遇的位置为环上的第1个点。

  整理一下:快慢指针在环上必然相遇,相遇即坐标相差r,首次相遇时n=kr;然后快指针回到原点,二者以相同速度1各自前进,当快指针(注意速度已经恢复正常)到达环的第1个点(坐标为x)时,走过x步,而慢指针也走过x步,来到n+x,此时快慢指针相差为n,正好是r的整倍数,所以它们相遇在环的第1个节点(坐标x位置)。

typedef struct node {
    struct node *next;
    int value;
} node_t;

node_t*
get_ring_head(node_t *list)
{
    node_t *fast = list;
    node_t *slow = list;
    
    while (fast && fast->next) {
        // 快慢指针都是从第1个节点开始, 开始是相等的, 所以先移动后判断
        fast = fast->next->next;
        slow = slow->next;
        
        // 首次相遇
        if (fast == slow) {
            fast = list;
            // 当再次相遇时循环退出
            while (fast != slow) {
                fast = fast->next;
                slow = slow->next;
            }
            return (fast);
        }
    }
    
    return (NULL);
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小瓶子36

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值