链表练习(八)— 两个链表相交问题(链表中可能存在环)

题目:
给定两个单链表的头节点 headA 和 headB (headA和headB可能有环也可能无环),请找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
在这里插入图片描述
环状链表。
在这里插入图片描述

分析:

  1. 首先判断两个单链表是否是环链表,如果是,则找出各自的环相交点处loopNode,如果不是,则return null。
  2. 根据各组相交点,判断两个两个链表相交情况
    2.1 如果loopNode一个为null,一个不为null,则说明两个链表一定不想交
    2.2 如果两个loopNode都为null,则遍历两个链表,取其长度,将长的链表遍历掉多余的长度,使两个链表等长,而后一同遍历并进行比较,如果相等,则说明两个链表的相交点就是该处,直接return。
    2.3 如果 loopNode不为null,并且 loopNode1 == loopNode2,则说明两个链表环处相交点相同,则将loopNode作为tail,其余同2.2
    2.4 如果loopNode不同,则说明在环上相交,则取其中任意一链表,遍历环处一圈,看在何处与另一节点相同。

代码实现:

 public static class ListNode {
        private int value;
        private ListNode next;

        public ListNode(int v) {
            this.value = v;
        }

        public ListNode(int v, ListNode node) {
            value = v;
            next = node;
        }
    }

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA == null || headB == null){
            return null;
        }

        ListNode loopNode1 = getLoopNode(headA);
        ListNode loopNode2 = getLoopNode(headB);
        //loopNode 一个为null 另一个不为null ,则两个链表一定不会相交。
        if (loopNode1 == null && loopNode2 == null){
            return noLoop(headA,headB);
        }

        if(loopNode1 != null && loopNode2 != null){
            return bothLoop(headA,loopNode1,headB,loopNode2);
        }
        return null;
    }

    //说明headA  headB 都是环状链表,这种情况下,求第一次相交节点共有2中情况
    //1. 节点相交处 在head ~ loopNode之前(头节点到环的相交节点之前,headA和headB就已经相交)
    //2. 在环中相交。
    public static  ListNode bothLoop(ListNode headA, ListNode loopNode1, ListNode headB, ListNode loopNode2) {
        ListNode cur1 = null;
        ListNode cur2 = null;
        //第一种情况:两个链表都是环链表,并且环的第一个相交点相同,那就是在head ~ loopNode之间两个链表第一次相交
        if (loopNode1 == loopNode2 ){
            cur1 = headA;
            cur2 = headB;
            int n = 0;
            //将loopNode 作为tail节点,同 noLoop方法
            //遍历到loopNode1,找到cur1和cur2的长度
            while (cur1 != loopNode1){
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loopNode2){
                n--;
                cur2 = cur2.next;
            }
            //长的链表赋值给cur1,短的cur2
            cur1 = n < 0 ? headB : headA;
            cur2 = cur1 == headA ? headB : headA;
            n = Math.abs(n);
            //遍历多余的部分,使cur1和cur2一样长
            while (n != 0){
                n--;
                cur1 = cur1.next;
            }
            //一起向下遍历,直到cur1 = cur2
            while (cur1 != cur2){
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        }else{
            //如果cur1 != cur2说明两个链表在环处相交点不同
            //将cur1绕着loopNode1遍历一圈,找到期间和cur2相交的点
            cur1 = loopNode1.next;
            while (cur1 != loopNode1){
                if (cur1 == loopNode2){
                    //return loopNode1和loopNode2都行,看离哪个链表近一些
                    return loopNode1;
                }
                cur1 = cur1.next;
            }
            return null;
        }
    }

    //判断单链表是否是环状链表,如果是 返回首次相交节点,如果不是,返回null
    public static ListNode getLoopNode(ListNode head) {
        if (head == null || head.next == null || head.next.next == null) {
            return null;
        }
        ListNode fast = head.next.next;
        ListNode slow = head.next;

        while (fast != slow) {
            //说明链表不是环状,直接return
            if (fast.next == null || fast.next.next == null) {
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        //走到这,说明
        //1.是环状链表
        //2.fast 和 slow 相遇了一次
        fast = head;
        //第一次相遇后,fast节点从head处一步一步再次向下走,一定会和slow再次相遇。
        //fast 和 slow 再次相遇 就是环状链表的首次相交节点
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

    //说明loop1 和 loop2为null,两个单链表都不是环状链表
    //那就要看两个链表是否相交(内存地址相同)
    public static ListNode noLoop(ListNode headA,ListNode headB){

        ListNode cur1 = headA;
        ListNode cur2 = headB;
        int n = 0;
        //遍历cur1 和 cur2 获取到两个链表长度 以及tail节点
        //循环条件是 next 不为null,这样就可以获取到tail节点
        while (cur1.next != null){
            n++;
            cur1 = cur1.next;
        }
        while (cur2.next != null){
            n--;
            cur2 = cur2.next;
        }
        //如果遍历完后,两个链表的tail 节点不相同,说明肯定不想交,直接return null。
        if (cur1 != cur2 ){
            return null;
        }
        // 将长度长的链表 赋给cur1,短的赋给cur2
        cur1 = n > 0 ? headA : headB;
        cur2 = cur1 == headA ? headB : headA;
        n = Math.abs(n);
        //n取绝对值后, 让长的cur1链表先跑到和cur2的长度相等。
        while (n != 0){
            n--;
            cur1 = cur1.next;
        }
        //两个链表长度相等开始遍历,同时向下跑,看什么时候相等,则就是第一次相交的节点。
        while (cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }

        return cur1;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值