手撕代码 之 环形链表

1. 环形链表的判定

  • 问题描述

    给定一个单链表,判断链表中是否存在环。
  • 解题思路

    设定两个指针:快指针_fast与慢指针_slow。 二者从链表头节点同时开始向后移动,快指针每次移动2步,即_fast = _fast.next.next;慢指针每次移动1步,即_slow = _slow.next。
    若链表中存在环,那么在经过若干步后,二者一定能够相遇;否则,快指针_fast则会到达NULL。
    并且,由于在每一步中,_fast指针都会比慢指针_slow多走一步,所以在二者相遇的时刻,慢指针一定还没有走完这个环
    class Node{
      int val;
      Node next;
    }
    
    public boolean isCyclic(Node head){
      if(head==null)  return false;
      Node fast = head, slow = head;
      while(slow!=null && fast!=null && fast.next!=null){
        fast = fast.next.next;
        slow = slow.next;
        if(slow == fast)
          return true;
      } 
      return false;
    }  
    

2. 环形链表的环入口

  • 问题描述

    给定一个有环单链表,找到该有环链表的入环节点。
  • 解题思路

    1 的基础上,我们知道在二者相遇时_slow还没有走完链表。设两个指针的相遇点距离入环点的距离为x,链表起点距离入环点的距离为a,链表的总长度为L,环的长度为r,则有
  • 慢指针走的距离: a + x
  • 快指针走的距离: a + x + n*r (n为快指针多走的圈数)
  • 由于慢指针每走一步,快指针走两步,故有: 2 * (a + x) = a + x + n * r
    所以: a + x = n * r
  • 我们对这个式子进行转换一下, 有: a = (r-x) + (n-1) * r

那么,这个式子说明了什么呢?很明显,等号左侧是从链表起点到入环节点的距离;对于等号右边,前一项(r-x) 表示的是从快慢指针相遇的位置到下一次来到入环节点的距离,后一项表示若干个环的距离。由此我们就得到了这个问题的答案:

当快慢指针相遇时,我们使用两个指针分别指向链表的头节点和相遇节点,然后二者同时移动,每次移动一步,那么下一次二者相遇的节点就是入环节点。

class Node{
  int val;
  Node next;
}

public boolean cyclicEntrance(Node head){
  if(head==null)  return null;
  Node fast = head, slow = head;
  Node p2 = null;
  while(slow != null && fast!=null && fast.next != null){
    fast = fast.next.next;
    slow = slow.next;
    if(slow == fast){
      p2 = slow;
      break;
    }
  } 
  Node p1 = head;
  while(p1 !=null && p2 != null){
    if(p1 == p2)  return p1;
    p1 = p1.next;
    p2 = p2.next;
  }
}  

3. 求解环上的节点数目

  • 问题描述

    给定一个有环单链表,求解环上的节点数目。
  • 解题思路

    • 思路 1

    1 中快慢指针的相遇节点开始,继续让快慢指针向前走,那么下一次相遇时,快指针一定比慢指针多走了一个环的距离。

    • 思路 2

    根据 2 中的入环节点,从它出发,下次在来到这个节点,那么就是走了一个环的距离。

    class Node{
      int val;
      Node next;
    }
    
    public boolean cyclicLenght(Node head){
      if(head==null)  return 0;
      Node fast = head, slow = head;
      while(slow != null && fast!=null && fast.next != null){
        fast = fast.next.next;
        slow = slow.next;
        if(slow == fast)
          break;
      } 
      int counter = 0;
      while(true){
        slow = slow.next;
        fast = fast.next.next;
        counter += 1;
        if(slow == fast)  return counter;
      }
    }  
    

4. 两个链表是否相交(拓展题)

  • 问题描述

    给定两个单链表 A 和 B,判断二者是否相交。

  • 解题思路

    对于这个问题,我们可以分三种情况讨论:

    1. 两个链表中均没有环;
    2. 两个链表中其中一个有环;
    3. 两个链表中均有环。

    对于情况1,除了采用Hash的方法,我们还可以采用两种更优雅的方式。
    (1) 我们可以将 A 的尾节点指向 B 的头节点,这样假设A 和 B存在交点,那么在这个链表中一定会存在一个环。这样就将这个题目转化为判断一个链表是否有环的问题,方法见1. 环形链表的判定
    (2) 假设两个链表存在交点,那么二者的尾节点一定是相同的。那么我们只需要将两个链表均找到其尾节点,判断是否相同即可。

    对于情况2,如果 A 和 B 中只有一个存在环,那么二者不可能存在相交的情况。假设A中有环,B中无环,而且A和B相交。那么不论交点在入环之前,还是在环上,B 都会到达A中的环,从而B中也会存在环,与假设矛盾。

    对于情况3,我们也可以采用两种不同的方式。
    (1) 找到 A 的入环点,将其断开,变成一个无环链表。这时候再去判断 B 是否同样变为无环链表。若是,则 A 和 B 相交,否则 A 和 B 不相交。
    (2) 同时找到 A 和 B 的入环点,判断是否相同,若相同,则 A 和 B 相交。如果不同,则从 B 的入环点开始对环进行遍历,若找到一个节点与 A 的入环点相同,那么二者相交,否则不相交。

    class Node{
      int val;
      Node next;
    }
    
    public boolean isIntersectant(Node head_a, Node head_b){
      if(head_a==null || head_b==null)  return false;
    
      // 判断 A 是否存在环
      boolean cyclic_a = isCyclic(head_a);
    
      // 判断 B 是否存在环
      Node slow = head_b, fast = head_b;
      Node slow_pre = null; // 用来记录slow之前的一个节点,在进行环路断开时使用
      boolean cyclic_b = false;
      while(slow != null && fast.next != null){
        fast = fast.next.next;
        pre_slow = slow;
        slow = slow.next;
        if(slow == fast)
          cyclic_b = true;
      } 
      if(cyclic_a^cyclic_b)  return false; //二者相异则返回false
      
      if(cyclic_b){ // 二者都有环
        // 找到b的入环点
        Node p_b = head_b;
        while(p_b!=null && slow!=null){
          if(p_b == slow)  break;
          p_b = p_b.next;
          slow_pre = slow;
          slow = slow.next;
        }
        slow_pre.next = null; // b 环路断开
        if(isCyclic(head_a))  return false; // b环路断开后,a仍然有环,则不相交
        else  return true;
      }else{ // 二者均无环
        Node p_a = head_a, p_b = head_b;
        while(p_a.next != null)  p_a = p_a.next; // a的尾节点
        while(p_b.next != null)  p_b = p_b.next; // b的尾节点
        if(p_a == p_b)  return true;
        else  return false;
      }
    }  
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 193,968评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,682评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,254评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,074评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,964评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,055评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,484评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,170评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,433评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,512评论 2 308
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,296评论 1 325
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,184评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,545评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,150评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,437评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,630评论 2 335

推荐阅读更多精彩内容