链表:常见面试题-求两个链表的第一个相交节点

文章提供了一个解决链表相交问题的算法,要求在O(N)的时间复杂度和O(1)的空间复杂度下完成。首先通过快慢指针检测链表是否有环,然后根据环的情况和链表是否相交进行不同的处理,最终找到相交的第一个节点或返回null。文章包含代码实现和不同情况的分析。
摘要由CSDN通过智能技术生成

题目:给定两个可能有环也可能无环的单链表,头节点分别为head1和head2.请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null

要求:如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度达到O(1)

这个题目堪称链表的爹题,能手写出这一道题基本上一般大厂的链表笔试面试就没有问题了

解这个题目之前建议看一下我的另外一个面试题的题解:

链表:常见面试题-Leetcode原题142环形链表II_鱼跃鹰飞的博客-CSDN博客

这个题目的解体分析,两个链表有以下几种情况

 

(1)两个链表都无环,这种情况是有可能相交的,相交的时候从相交点开始之后的每一个点都是相同的。

(2)一个有环,一个无环,肯定不会有相交节点,因为环肯定是链表的最后的那些节点,一旦相交后面节点都相同,不可能出现相交还一个有环一个无环的情况。

(3)两个都有环,这个分为两种大情况:相交和不相交 。相交又分为两种:入环节点是同一个节点和入环节点不是同一个节点(入环两个节点都可以认为是第一个相交节点,返回哪个都行)

代码实现如下:

package dataStructure.linkedList.practice;

import dataStructure.linkedList.ListNode;

public class FindFirstIntersect {
    /**
     * 返回两个链表的第一个相交节点
     * @param head1 第一个链表的头节点
     * @param head2 第二个链表的头节点
     * @return 第一个相交节点
     */
    public static ListNode findFirstIntersectNode(ListNode head1, ListNode head2) {
        //先找到每一个链表的第一个入环节点
        ListNode firstLoop1 = getLoopNode(head1);
        ListNode firstLoop2 = getLoopNode(head2);
        /**
         * 两个链表有以下集中情况
         * 1.两个链表都没有环,是有可能相交的
         * 2.链表1有环,链表2无环,不可能相交,因为相交之后所有的节点都会重合,有环到了入环节点之后就不可能出去了
         * 3.链表1无环,链表2有环,不可能相交,因为相交之后所有的节点都会重合,有环到了入环节点之后就不可能出去了
         * 4.两个都有环,是有可能相交的
         */
        if(firstLoop1 == null && firstLoop2 == null) {
            return getFirstIntersectNoLoop(head1, head2);
        } else if(firstLoop1 != null && firstLoop2 != null) {
            return getFirstIntersectAllLoop(head1, head2, firstLoop1, firstLoop2);
        } else {
            return null;
        }
    }

    /**
     * 确定两个链表都有环的情况下寻找第一个相遇的节点
     * @param head1 第一个链表的头节点
     * @param head2 第二个链表的头节点
     * @return
     */
    public static ListNode getFirstIntersectAllLoop(ListNode head1, ListNode head2, ListNode firstLoop1, ListNode firstLoop2) {
        //如果两个链表的第一个入环节点一样,说明一定相交,这个时候就需要找到哪个节点是第一个相交节点
        if(firstLoop1 == firstLoop2) {
            //把firstLoop1(也是firstLoop2)当成终点,与两个无环链表的做法一样寻找起点
            ListNode cur1 = head1;
            ListNode cur2 = head2;
            int count = 0;
            while(cur1 != firstLoop1) {
                cur1 = cur1.next;
                count ++;
            }
            while(cur2 != firstLoop1) {
                cur2 = cur2.next;
                count --;
            }
            cur1 = count >= 0? head1 : head2;
            cur2 = cur1 == head1? head2 : head1;
            count = Math.abs(count);
            while(count > 0) {
                cur1 = cur1.next;
                count --;
            }
            while(cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        } else {
            //如果第一个入环节点不一样,分为两种情况:1.两者不相交 2.两者相交只是入环节点不同
            ListNode node1 = firstLoop1.next;
            //两个链表是否相交
            boolean isIntersected = false;
            while(node1 != firstLoop1) {
                if(node1 == firstLoop2) {
                    //如果node1从firstLoop1开始走一直再走回来的过程中遇到了firstLoop2设置isIntersected为true
                    //如果一直未遇到设置isIntersected为false,表示两个链表不相交
                    isIntersected = true;
                    break;
                }
                //node1沿着环依次移动
                node1 = node1.next;
            }

            if(isIntersected) {
                //如果入环节点不同并相交说明两者就是相交在环上,返回两者入环节点哪个都行
                return firstLoop1;
            } else {
                return null;
            }
        }
    }
    /**
     * 确定两个链表都无环的情况下寻找第一个相遇的节点
     * @param head1
     * @param head2
     * @return
     */
    public static ListNode getFirstIntersectNoLoop(ListNode head1, ListNode head2) {
        int count = 0;
        ListNode cur1 = head1;
        ListNode cur2 = head2;
        while(cur1 != null) {
            count ++;
            cur1 = cur1.next;
        }
        while(cur2 != null) {
            count --;
            cur2 = cur2.next;
        }

        cur1 = count >= 0? head1 : head2;
        cur2 = cur1 == head1? head2 : head1;
        count = Math.abs(count);
        while(count > 0) {
            cur1 = cur1.next;
            count --;
        }
        while(cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }
    /**
     * 获取链表的第一个入环节点,无环返回null
     * @param head 链表的头节点
     * @return
     */
    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;
        //如果slow和fast没有相遇
        while(slow != fast) {
            //如果fast已经为空,或者它的next为空,不管哪个为空,都说明无环
            if(fast == null || fast.next == null) {
                //无环返回null
                return null;
            }
            //快指针一次跳两个节点
            fast = fast.next.next;
            //慢指针一次跳一个节点
            slow = slow.next;
        }
        //走到这里(跳出循环且没有返回)说明确实有环
        //fast退回head位置,每次走一步
        fast = head;
        //跳出循环一定是第一次相遇的节点
        while(slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        //返回fast或者slow都行
        return slow;
    }

    public static void main(String[] args) {
        ListNode n1 = new ListNode(1);
        ListNode n2 = new ListNode(2);
        ListNode n3 = new ListNode(3);
        ListNode n4 = new ListNode(4);
        ListNode n5 = new ListNode(5);
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n4.next = n5;
        n5.next = n3;
        ListNode n6 = new ListNode(6);
        n6.next = n4;
        /*ListNode firstLoop = getLoopNode(n1);
        System.out.println(firstLoop.value);*/
        ListNode first = findFirstIntersectNode(n1, n6);
        System.out.println(first == null? null : first.val);

        ListNode n7 = new ListNode(7);
        ListNode n8 = new ListNode(8);
        ListNode n9 = new ListNode(9);
        n7.next = n8;
        n8.next = n9;
        n9.next = n8;
        first = findFirstIntersectNode(n1, n7);
        System.out.println(first == null? null : first.val);


        /**
         * 测试第一个入环节点 [3,2,0,-4]
         */
        ListNode n11 = new ListNode(3);
        ListNode n12 = new ListNode(2);
        ListNode n13 = new ListNode(0);
        ListNode n14 = new ListNode(-4);
        n11.next = n12;
        n12.next = n13;
        n13.next = n14;
        n14.next = n12;
        ListNode firstLoopTest = getLoopNode(n11);
        System.out.println(firstLoopTest == null ? null : firstLoopTest.val);
    }
}

应该是有Leetcode原题了,忘了是哪个了,代码只是为了实现功能,比较粗略,大家看不明白的可以私信我,发现错误也欢迎批评指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值