剑指offer-37-两个链表的第一个公共结点

题目

输入两个链表,找出它们的第一个公共结点。
(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,
保证传入数据是正确的)

    两个链表的公共节点不会是链表的第一个结点,若是有公共结点,则公共结点之后的部分全都是公共的。

接下来看一下解题思路:

思路一:

    首先想到的就是:遍历第一个链表上的每个节点,每遍历到一个节点的时候,在第二个链表上顺序遍历每个节点。若在第二个链表上有一个节点和第一个链表上的节点一样,说明两个链表在这个节点上重合,就是他们的公共节点

代码示例:

public ListNode FindFirstCommonNode1(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }

        for (ListNode p1 = pHead1; p1 != null; p1 = p1.next) {
            for (ListNode p2 = pHead2; p2 != null; p2 = p2.next) {
                if (p1 == p2) {
                    return p1;
                }
            }
        }

        return null;
    }
总结

这里需要注意的是

  1. 这种方法时间复杂度比较大,如果第一个链表长为m,第二个链表长为n;那么时间复杂度就是O(mn);
思路二:

    根据链表的定义,若是两个链表有公共节点,则这两个链表的公共节点之后一定都是共用的,所以可以定义两个
辅助空间(栈),分别用来存放两个链表,这样两个链表的尾结点就位于栈顶,接下来比较两个栈顶的节点是否相同,若相同就弹出栈,比较下一个,直到找到最后一个相同的结点。

代码示例:

public ListNode FindFirstCommonNode2(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }

        Stack<ListNode> stack1 = new Stack<>();
        Stack<ListNode> stack2 = new Stack<>();
        //链表1入栈
        while (pHead1 != null) {
            stack1.push(pHead1);
            pHead1 = pHead1.next;
        }
        //链表2入栈
        while (pHead2 != null) {
            stack2.push(pHead2);
            pHead2 = pHead2.next;
        }
        //用于保存最后一个相同的节点
        ListNode current = null;
        //stack1.peek() 只是查看栈顶元素,并不删除
        //stack1.pop() 删除并返回栈顶元素
        while (!stack1.isEmpty() && !stack2.isEmpty() && stack1.peek() == stack2.pop()) {
            current = stack1.pop();
        }
        return  current;
    }
总结

这里需要注意的是

  1. 这种方法通过两个辅助空间换取时间,时间复杂度就是O(m + n);
思路三:

    通过hashSet,先将第一个链表放到hashSet里面,再依次遍历第二个链表的每个节点, 若遇到相同的节点就返回。

代码示例:

public ListNode FindFirstCommonNode4(ListNode pHead1, ListNode pHead2){
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        Set<ListNode>  nodeLists = new HashSet<>();

        for (ListNode p = pHead1; p != null; p = p.next) {
            nodeLists.add(p);
        }
        for (ListNode p = pHead2; p != null; p = p.next) {
            if (nodeLists.contains(p)) {
                return p;
            }
        }

        return null;
}
总结

这里需要注意的是

  1. 这种方法和思路二一样,也是通过一个Set辅助,时间复杂度是O(m + n);
思路四

    首先遍历两个链表得到他们的长度,算出长链表比短链表多几个结点, 在第二次遍历的时候,较长的链表先走若干步,接着再同时在两个链表上遍历,找到的第一个相同的结点就是公共结点;

代码示例:

public ListNode FindFirstCommonNode3(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        int count1 = getCount(pHead1);
        int count2 = getCount(pHead2);

        if (count1 > count2) {
            p1 = getCommon((count1 - count2), p1);
        }
        if (count1 < count2) {
            p2 = getCommon((count2 - count1), p2);
        }

        return findCommon(p1, p2);
    }
    //寻找公共结点
    private ListNode findCommon(ListNode pHead1, ListNode pHead2) {
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;

        while (p1 != null && p2 != null) {
            if (p1 == p2) {
                return p1;
            }
            p1 = p1.next;
            p2 = p2.next;
        }

        return null;
    }
    //长的链表先走多余的几步
    private ListNode getCommon(int count, ListNode pHead1) {
        ListNode p = pHead1;
        for (int i = 0; i < count; i++){
            p = p.next;
        }
        return p;
    }
    //获取链表的结点个数
    private int getCount(ListNode head) {
        ListNode p = head;
        int i = 0;
        while (p != null) {
            p = p.next;
            i++;
        }
        return i;
    }

总结
    这种方法不需要借助辅助空间,而且时间复杂度为O(m+n)。

思路五(双指针法)

参考LeetCode交叉链表
    首先创建两个头指针p1、 p2;分别指向链表headA和headB;然后依次向后遍历遍历,如果p1先到达链表尾部,则从headB链表头部重新开始遍历,类似的:如果p2先到达链表尾部,则从headA链表头部重新开始遍历,若在某一时刻 p1 和 p2 相遇,则 p1/p2 为相交结点.
为什么会这样呢?看一下官方给出的解释
想弄清楚为什么这样可行, 可以考虑以下两个链表:
A={1,3,5,7,9,11} 和 B={2,4,9,11},相交于结点 9。
由于 B.length (=4) < A.length (=6),pB 比 pA 少经过 2 个结点,会先到达尾部。将 pB 重定向到 A 的头结点,pA 重定向到 B 的头结点后,pB 要比 pA 多走 2 个结点。因此,它们会同时到达交点。
如果两个链表存在相交,它们末尾的结点必然相同。因此当 pA/pB 到达链表结尾时,记录下链表 A/B 对应的元素。若最后元素不相同,则两个链表不相交。
看了官方的思路,确实不一般,很巧妙。
来源:力扣(LeetCode)

代码示例:

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA == null || headB == null) {
            return null;
        }
        ListNode p1 = headA;
        ListNode p2 = headB;
        while(p1 != p2) {
            p1 = (p1 == null ? headB : p1.next);
            p2 = (p2 == null ? headA : p2.next);
        }
        return p1;
    }

总结
    这种方法不需要借助辅助空间,而且
时间复杂度 : O(m+n)。
空间复杂度 : O(1)。
    写完这个算法,看了一下评论区,发现了关于这个算法的一些浪漫 😮。
我走过你走过的路,你吹过我吹过的晚风,错的人迟早会走散,而对的人迟早会相逢!
想想这也算是学习的快乐吧!!!哈哈哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值