剑指Offer-38-两个链表的第一个公共节点

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dawn_after_dark/article/details/81572093

题目

输入两个链表,找出它们的第一个公共结点。

解析

预备知识

2个单向链表相交后的示意图如下所示:
这里写图片描述
从上图的得知,若两个链表相交,那么这两个链表应该具有相同的尾部,也就是说呈现出Y型。因为单向链表中只有一个next域指向后继结点,所以从第一个相交点开始都是两个链表的公共部分,而不是我们思维惯性以为相交后就岔开了,不明白的再仔细看看上图即可。

思路一

空间换时间的做法,既然若相交,必有公共节点。所以我们可以申请一个set,遍历第一个链表,存放所有的节点。然后遍历第二个节点,若发现set中已存在该节点,那么就说明从这个节点开始两个链表相交,该节点就是第一个公共节点。

    /**
     * 空间换时间
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        if(pHead1 == null || pHead2 == null) {
            return null;
        }
        Set<ListNode> set = new HashSet<>();
        while(pHead1 != null) {
            set.add(pHead1);
            pHead1 = pHead1.next;
        }
        while(pHead2 != null) {
            if(set.contains(pHead2)) {
                return pHead2;
            }
            pHead2 = pHead2.next;
        }
        return null;
    }

思路二

尝试从不申请额外空间的做法。我们发现若两个链表相交,最后一个节点必然相同。所以我们可以遍历两个链表使其都走向最后一个节点。此时,若发现最后这两个节点不相同,则说明链表不相交。若相同,则说明链表相交。但是最后一个节点可能不是第一个相交点,我们需要向前回溯寻找第一个相同相交点。对于这种先考察后进来的节点,也就是后进先出的情况,使用栈再好不过了,只需在第一步遍历的时候把链表各自的节点放到各自的栈中,最后不断比较栈顶即可。若栈顶相同,保存该栈顶,然后弹出,继续比较,直到栈为空或者栈顶不相同,那么上一次的栈顶就是第一个公共节点。但是这个做法还是借助了额外的空间,故代码实现就不贴出了。 但是你也可以尝试递归,哈哈,我就不写了,挺简单的。
我们要解决就是如何找到第一个相交点,由于链表的特性,只能从前往后遍历。所以问题转化为如何从开头遍历寻找第一个相交点。之前的分析已知知道了,若两个链表相交,必然有公共的尾部。我们的目标是如何使两个链表可以同时走到公共尾部的第一个节点。因为公共尾部的第一个节点到开头各自不同,所以我们求出2个链表的长度的差值,使长的链表先走这个差值长度,然后再同时走两个链表,这时就可以不断比较当前遍历节点的地址是否相同即可。

    /**
     * 相交的链表必有相同的尾部
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode FindFirstCommonNode2(ListNode pHead1, ListNode pHead2) {
        if(pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode p = pHead1, q = pHead2;
        int length1 = 1, length2 = 1;
        while(p.next != null) {
            p = p.next;
            length1++;
        }
        while(q.next != null) {
            q = q.next;
            length2++;
        }
        if(p == q) {
            int diff = Math.abs(length1 - length2);
            if(length1 > length2) {
                p = pHead1;
                q = pHead2;
            } else {
                p = pHead2;
                q = pHead1;
            }
            while(diff-- > 0) {
                p = p.next;
            }
            while(p != q) {
                p = p.next;
                q = q.next;
            }
            return p;
        }
        return null;
    }

思路三

牛逼的做法,牛逼的思路,异常的清爽代码。
该思路的做法也是如何解决思路中使两个链表遍历到离尾部相同长度的位置。思路二使用是事先走一遍统计长度,然后使长的链表先走差值步长,这样就可以保证2个链表现在离尾部相同距离相同。
该思路则是采用了2个链表互补形成2者长度相同的做法。比如我两个棍子长度为n,m,我可以把m拼在n后面,n拼在m后面。这样我就有2个相同长度的棍子,n+m。我们实际的做法,并不是真正的拼接,而是跳转。
1. p,q指向各自的链表
2. 判断p与q是否相同,不相同,判断p是否走到链表1的末尾,若是,则p接着从链表2头部开始走,若没有,则继续遍历下一个节点
3. 判断q是否走到链表2的末尾,若是,则q接着从链表1头部开始走,若没有,则继续遍历下一个节点

以上出现结果可能有以下几种:
1. 当链表1与链表2长度一致时,那么在各自链表上遍历即可完成判断是否有相交点
2. 因为当长度一致时,两个链表齐头并进,若有公共尾部,必然能走到相交点,若没有,最后都会等于null而结束循环
3. 当链表1与链表2长度不一致时,短的链表的会率先结束自己的遍历,并开始在长的链表中遍历。当长的链表完成自己的遍历时,并开始在短的链表遍历时。这时在长的链表遍历已经走完了两者差值长度。因为是同时走的,这时他们走的距离是相同的,因此他们对于到末尾的距离也是相同,因为2者总长都为n + m。剩下就是继续判断是否相交了,因为保证了到末尾的距离一样,所以必然能同时走到第一个相交点。

    /**
     * 利用补齐法达到使两个链表长度相同
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode FindFirstCommonNode3(ListNode pHead1, ListNode pHead2) {
        ListNode p = pHead1, q = pHead2;
        while(p != q) {
            p = p == null ? pHead2 : p.next;
            q = q == null ? pHead1 : q.next;
        }
        return p;
    }

总结

可以结合画图来发掘思路,但是请注意以上的解法的都是假设链表不存在环。若存在环,则可能稍微复杂一点,具体可以参考如何判断两个链表是否相交并求出相交点如何判断单链表是否有环、环的入口、环的长度和总长

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/81572093