【刷题】求两单向链表交点(可能有环)

//Java版.左老师给出的代码,很赞
   //package problems_2017_08_16;
   
   public class Problem_03_FindFirstIntersectNode {
   
       public static class Node {
           public int value;
           public Node next;
   
          public Node(int data) {
              this.value = data;
          }
      }
  
      public static Node getIntersectNode(Node head1, Node head2) {
          if (head1 == null || head2 == null) {
              return null;
          }
          Node loop1 = getLoopNode(head1);
          Node loop2 = getLoopNode(head2);
          if (loop1 == null && loop2 == null) {
              return noLoop(head1, head2);
          }
          if (loop1 != null && loop2 != null) {
              return bothLoop(head1, loop1, head2, loop2);
          }
          return null;
      }
  
      public static Node getLoopNode(Node head) {
          if (head == null || head.next == null || head.next.next == null) {
              return null;
          }
          Node n1 = head.next; // n1 -> slow
          Node n2 = head.next.next; // n2 -> fast
          while (n1 != n2) {
              if (n2.next == null || n2.next.next == null) {
                  return null;
              }
              n2 = n2.next.next;
              n1 = n1.next;
          }
          n2 = head; // n2 -> walk again from head
          while (n1 != n2) {
              n1 = n1.next;
              n2 = n2.next;
          }
          return n1;
      }
  
      public static Node noLoop(Node head1, Node head2) {
          if (head1 == null || head2 == null) {
              return null;
          }
          Node cur1 = head1;
          Node cur2 = head2;
          int n = 0;
          while (cur1.next != null) {
              n++;
              cur1 = cur1.next;
          }
          while (cur2.next != null) {
              n--;
              cur2 = cur2.next;
          }
          if (cur1 != cur2) {
              return null;
          }
          cur1 = n > 0 ? head1 : head2;
          cur2 = cur1 == head1 ? head2 : head1;
          n = Math.abs(n);
          while (n != 0) {
              n--;
              cur1 = cur1.next;
          }
          while (cur1 != cur2) {
              cur1 = cur1.next;
              cur2 = cur2.next;
          }
          return cur1;
      }
  
      public static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {
          Node cur1 = null;
          Node cur2 = null;
          if (loop1 == loop2) {
              cur1 = head1;
              cur2 = head2;
              int n = 0;
              while (cur1 != loop1) {
                  n++;
                  cur1 = cur1.next;
              }
              while (cur2 != loop2) {
                  n--;
                  cur2 = cur2.next;
              }
              cur1 = n > 0 ? head1 : head2;
              cur2 = cur1 == head1 ? head2 : head1;
             n = Math.abs(n);
             while (n != 0) {
                 n--;
                 cur1 = cur1.next;
             }
             while (cur1 != cur2) {
                 cur1 = cur1.next;
                 cur2 = cur2.next;
             }
             return cur1;
         } else {
             cur1 = loop1.next;
             while (cur1 != loop1) {
                 if (cur1 == loop2) {
                     return loop1;
                 }
                 cur1 = cur1.next;
             }
             return null;
         }
     }
 
     public static void main(String[] args) {
         // 1->2->3->4->5->6->7->null
         Node head1 = new Node(1);
         head1.next = new Node(2);
         head1.next.next = new Node(3);
         head1.next.next.next = new Node(4);
         head1.next.next.next.next = new Node(5);
         head1.next.next.next.next.next = new Node(6);
         head1.next.next.next.next.next.next = new Node(7);
 
         // 0->9->8->6->7->null
         Node head2 = new Node(0);
         head2.next = new Node(9);
         head2.next.next = new Node(8);
         head2.next.next.next = head1.next.next.next.next.next; // 8->6
         System.out.println(getIntersectNode(head1, head2).value);//output:6
 
         // 1->2->3->4->5->6->7->4...
         head1 = new Node(1);
         head1.next = new Node(2);
         head1.next.next = new Node(3);
         head1.next.next.next = new Node(4);
         head1.next.next.next.next = new Node(5);
         head1.next.next.next.next.next = new Node(6);
         head1.next.next.next.next.next.next = new Node(7);
         head1.next.next.next.next.next.next.next = head1.next.next.next; // 7->4
 
         // 0->9->8->2...
         head2 = new Node(0);
         head2.next = new Node(9);
         head2.next.next = new Node(8);
         head2.next.next.next = head1.next; // 8->2
         System.out.println(getIntersectNode(head1, head2).value);//output:2
 
         // 0->9->8->6->4->5->6..
         head2 = new Node(0);
         head2.next = new Node(9);
         head2.next.next = new Node(8);
         head2.next.next.next = head1.next.next.next.next.next; // 8->6
         System.out.println(getIntersectNode(head1, head2).value);//output:4
         System.out.println(getIntersectNode(head2, head1).value);//note the order //output:6
 
     }
 
 }

然后照着程序执行流程梳理思路:

0.判断两链表是否有环(分别找环入口结点,能找到则有环,否则无环):

若都无环,转入第1步(可能是情况1或2);

若都有环,转入第2步(可能是情况4或5或6);

若一个有环一个无环,直接返回NULL,因为如果他们相交,是不可能一个有环一个无环的(图中情况3);

1.都无环的情况,退化到两个无环链表找入口点的问题(可参见<剑指offer>和leetcode:Intersection of Two Linked Lists)

1.0 先判断两条链表的长度;

1.1 从头节点开始走,更长的链表先走"长度之差"步,然后一起走,如果相遇,则为入口点(情况2);否则无交点(情况1)

2.都有环的情况,这种情况还要细分:

2.0 先判断两链表环入口点是否相同,若相同,则为情况4,转入步骤2.1;若不同,则为情况5或6,转入2.2;

2.1 如果为上图中情况4,我们可以把两链表交点作为"假想的尾部节点",然后就退化成两个无环链表找交点的问题了;

2.2 为判断两链表是否有交点,我们可以从第一个环的入口节点的下一个节点开始next,如果遇到了第二个链表环的入口节点,则返回第一个链表的入口节点(情况5:题目说找出第一个相交的节点,其实我觉得返回第二个链表的入口节点也行);反之,若走了一圈没遇到第二个链表环的入口节点,说明两链表不相交(情况6);

至此,程序执行结束.设计很巧妙,要熟练掌握.

另外一段代码:

//综合:链表噩梦题之一:2条链表相交问题,链表可能有环,也可能无环,求交点
    public static Node nightMireCrossNode(Node head1, Node head2){
        if (head1 == null || head2 == null) return null;

        Node loop1 = getLoopNodeFace(head1);
        Node loop2 = getLoopNodeFace(head2);

        //(1)`loop1 == null && loop2 == null` 俩都无环,求相交点
        if (loop1 == null && loop2 == null) return noLoopNodeGetCross(head1, head2);
        //(2)`if(loop1 != null && loop2 == null)`
        // 或者:`if(loop1 == null && loop2 != null)`
        // 其中一个有环,有一个无环,求相交点
        else if (loop1 != null && loop2 != null) return bothHasLoopCross(head1, head2, loop1, loop2);
        //(3)`if(loop1 != null && loop2 != null)` 俩都有环,求相交点
        else return null;//压根没有的
    }

//对于环形链表来说,**求入环节点**就是一个玄学的数学推理
    //咱不用推,直接记住以下结论:
    //
    //(1)初始化快慢指针:slow先走1步,fast先走2步;
    //(2)如果快指针,先遇到null,则根本就不可能有环,返回null
    //(3)有环的话,slow和fast一定会相遇,这第一次相遇就证明了有环,就是这么巧!!!
    //(4)怎么找到那个入环节点呢?让fast回到head,和slow同步慢走(都只走1步),第二次相遇时,这个点,就是入环节点。
    public static Node getLoopNodeFace(Node head){
        if (head == null || head.next == null) return null;
        //(1)初始化快慢指针:slow先走1步,fast先走2步;
        Node slow = head.next;
        Node fast = head.next.next;
        //(2)如果快指针,先遇到null,则根本就不可能有环,返回null
        while (slow != fast){
            if (fast.next == null || fast.next.next == null) return null;
            //(3)有环的话,slow和fast一定会相遇,这第一次相遇就证明了有环,就是这么巧!!!
            fast = fast.next.next;
            slow = slow.next;
        }
        //(4)怎么找到那个入环节点呢?
        // 让fast回到head,和slow同步慢走(都只走1步),第二次相遇时,这个点,就是入环节
        fast = head;
        while (slow != fast){
            slow = slow.next;
            fast = fast.next;//同步走
        }
        //第二次相遇,必然是入环节点
        return slow;
    }

//给你2个链表和他们的入环节点,如果都没有环,求相交点
    //(1)让cur1走head1,cur2走head2
    //(2)如果走完之后,发现,cur1 = cur2,显然就是相交的
    //如何确定相交的交点呢?
    //(3)如果长的那根链表长度为n1,短的那根链表长度为n2,因为末端都是相同的,则必然
    //n1-n2就是交点之前,head1比head2多出来的长度
    //故,让cur从新从head1走n1-n2,再让head2和head1同步走,必定同时走到交点处。
    public static Node noLoopNodeGetCross(Node head1, Node head2){
        //既然没有环,那也不会有入环节点,就不用串loo1和loop2了
        if (head1 == null || head2 == null) return null;//压根没有第二条链表

        //用n来模拟长链和断链长度的差
        int n = 0;
        //(1)让cur1走head1,cur2走head2
        Node cur1 = head1;
        Node cur2 = head2;
        while (cur1 != null) {
            n++;//计数长度
            cur1 = cur1.next;
        }
        while (cur2 != null) {
            n--;//head2走就要--,最后n就是长短只差
            cur2 = cur2.next;
        }
        //(2)如果走完之后,发现,cur1 = cur2,显然就是相交的
        if (cur1 != cur2) return null;//两个链表平行,不想交

        //否则就是相交的
        //咱需要把长链给cur1,短链给cur2
        cur1 = n >= 0 ? head1 : head2;
        cur2 = cur1 == head1 ? head2 : head1;
        //然后差值将其正数画,现在长链已经在cur1上,n必须>0
        //让长链走线n,弥补差值
        n = n < 0 ? -n : n;
        while (n != 0){
            n--;
            cur1 = cur1.next;
        }
        //然后cur1和cur2同步走
        while (cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        //一旦相遇,就是交点。

        return cur1;
    }


//给你2个链表和他们的入环节点,如果都有环,求相交交点
    //又分为以下三种小情况:
    //1)俩的环是独立的,压根不相交;
    //2)两个相交,而且入环节点是同一个;
    //3)2个链表相交,但是入环节点不相同;
    public static Node bothHasLoopCross(Node head1, Node head2,
                                        Node loop1, Node loop2){
        //俩交于1一个点,提前交,或者直接在入环节点loop1=loop2那交
        //2)两个相交,而且入环节点是同一个;
        if (loop1 == loop2){
            //用n来模拟长链和断链长度的差
            int n = 0;
            //(1)让cur1走head1,cur2走head2
            Node cur1 = head1;
            Node cur2 = head2;
            while (cur1 != loop1) {//从null替换为loop1
                n++;//计数长度
                cur1 = cur1.next;
            }
            while (cur2 != loop1) {//从null替换为loop1
                n--;//head2走就要--,最后n就是长短只差
                cur2 = cur2.next;
            }
            //(2)如果走完之后,发现,cur1 = cur2,显然就是相交的
            if (cur1 != cur2) return null;//两个链表平行,不相交

            //否则就是相交的
            //咱需要把长链给cur1,短链给cur2
            cur1 = n >= 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            //然后差值将其正数画,现在长链已经在cur1上,n必须>0
            //让长链走线n,弥补差值
            n = n < 0 ? -n : n;
            while (n != 0){
                n--;
                cur1 = cur1.next;
            }
            //然后cur1和cur2同步走
            while (cur1 != cur2){
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            //一旦相遇,就是交点。不论是交在前面,还是刚刚好交在loop1=loop2处,都是cur1
            return cur1;
        }else {
            //1)俩的环是独立的,压根不相交;
            //3)2个链表相交,但是入环节点不相同;
            //求交点怎么求呢???咱们可以这么看1)3)
            //设定cur1,让他们从loop1.next开始走,如果最后cur1竟然与loop2相遇,说明一定是相交的,交点也就是loop1或者loop2,随意
            //如果cur1回到loop1了,一直没有遇到loop2,说明压根不相交。
            Node cur1 = loop1.next;
            while (cur1 != loop1){
                if (cur1 == loop2) return loop2;//3)2个链表相交,但是入环节点不相同;现在找到了交点,任选其一
                cur1 = cur1.next;
            }
            //1)俩的环是独立的,压根不相交;
            return null;
        }
    }

好的参考文章
https://blog.csdn.net/weixin_46838716/article/details/124577412

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

叹了口丶气

觉得有收获就支持一下吧~

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

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

打赏作者

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

抵扣说明:

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

余额充值