[剑指offer学习心得]之:两个链表的第一个公共结点

本文介绍了一种高效算法来确定两个链表的第一个公共结点,通过比较链表长度并同步遍历来降低时间复杂度至O(m+n),避免使用额外的空间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

看到这道题,第一个想到的自然是暴力法:第一链表上顺序遍历每个结点,每遍历到一个结点,在第二个链表上顺序遍历每个结点。如果在第二个链表上有一个结点和第一个链表上结点相同,说明两个链表在这个结点上重合,于是找到了它们的公共结点。如果一个长度为m,一个长度为n,时间复杂度就是O(mn)

厉害了word哥,找个公共结点花了这么长时间,有没有其他办法?

其实还是有的,我们分析一下有公共结点的两个链表的特点就知道了。这两个链表从某一个结点开始,它们的next都指向同一个结点,所以从第一个公共结点开始,之后它们所有的结点都是重合的不可能出现分叉。

所以我们可以发现,如果两个链表有公共结点,那么公告结点出现在两个链表尾部,如果从后面往前比较,最后一个相同的不就是我们要找的第一个公告结点了嘛?从后往前,可以用到栈的方法,分别把两个链表结点放入两个栈里,这样两个链表的尾结点位于两个栈的栈顶,接下来比较两个栈顶的结点是否相同就可以了。

但是上面思路需要两个辅助的栈,是要耗费空间的。空间复杂度为O(m+n)。所以还有第三个思路:

首先遍历两个链表得到它们的长度,就知道哪个链表比较长,以及长的链表比短的链表多几个结点。第二次遍历时,较长的链表先走若干步,接着同时在两个链表上遍历,找到的第一个相同的结点就是它们的第一个公共结点。

第三种思路和第二种思路相比,时间复杂度相同,但是不需要辅助的栈,提高了空间效率,当然是更好的。

所以可以考虑代码了。

注意,在此之前,当然是要准备好测试用例:

  1. 功能测试(两个链表有公共结点:第一个公共结点在链表的中间、末尾、头部;两个链表没有公共结点)
  2. 特殊输入测试(输入的链表头结点为null)

代码实现:


public class FindFirstCommonNode {

    public static void main(String[] args) {
        test1();
        test2();
        test3();
        test4();
    }
    private static void test1() {
        // 第一个公共结点在链表中间
        // 1 - 2 - 3 \
        //            6 - 7
        //     4 - 5 /
        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);
        ListNode n6 = new ListNode(6);
        ListNode n7 = new ListNode(7);
        n1.next = n2;
        n2.next = n3;
        n3.next = n6;
        n6.next = n7;
        n4.next = n5;
        n5.next = n6;
        System.out.println(findFirstCommonNode(n1, n4)); // 6
    }
    private static void test2() {
        // 没有公共结点
        // 1 - 2 - 3 - 4
        //
        // 5 - 6 - 7
        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);
        ListNode n6 = new ListNode(6);
        ListNode n7 = new ListNode(7);
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n5.next = n6;
        n6.next = n7;
        System.out.println(findFirstCommonNode(n1, n5)); // null
    }
    private static void test3() {
        // 公共结点是最后一个结点
        // 1 - 2 - 3 - 4 \
        //                7
        //         5 - 6 /
        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);
        ListNode n6 = new ListNode(6);
        ListNode n7 = new ListNode(7);
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n4.next = n7;
        n5.next = n6;
        n6.next = n7;
        System.out.println(findFirstCommonNode(n1, n5)); // 7
    }
    private static void test4() {
        // 公共结点是第一个结点
        // 1 - 2 - 3 - 4 - 5
        // 两个链表完全重合
        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);
        ListNode n6 = new ListNode(6);
        ListNode n7 = new ListNode(7);
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n4.next = n5;
        System.out.println(findFirstCommonNode(n1, n1)); // 1
    }


    /**
     * 链表结点类
     */
    private static class ListNode {
        int val;
        ListNode next;
        public ListNode() {
        }
        public ListNode(int val) {
            this.val = val;
        }
        @Override
        public String toString() {
            return val + "";
        }
    }

    /**
     * 
     * @param head1 第一个链表
     * @param head2 第二个链表
     * @return 找到的公共结点,没有返回null
     */
    private static ListNode findFirstCommonNode(ListNode head1,ListNode head2){
        //首先得到两个链表的长度
        int length1=getListLength(head1);
        int length2=getListLength(head2);
        int dif=length1-length2;

        ListNode longListHead=head1;
        ListNode shortListHead=head2;
        if(dif<0){
            longListHead=head2;
            shortListHead=head1;
            dif=length2-length1;
        }

        //长链表先走几步
        for(int i=0;i<dif;i++){
            longListHead=longListHead.next;
        }

        while((longListHead!=null)&&(shortListHead!=null)&&(longListHead!=shortListHead)){
            longListHead=longListHead.next;
            shortListHead=shortListHead.next;
        }

        //得到第一个公共结点并返回
        return longListHead;
    }

    /**
     * 获取链表的长度
     * @param head
     * @return
     */
    private static int getListLength(ListNode head){
        int result=0;
        while(head!=null){
            result++;
            head=head.next;
        }
        return result;
    }
}

结果:

6
null
7
1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值