AC 此题,链表无敌!!!

刷过链表题目的小伙伴都应该有这样的体会,链表题最容易出错的就是 空指针异常 。做着做着“链断了”。因此,对于链表的题目来说,Coding 能力非常重要,通过大量题目的训练,练习对于 边界条件 判断的处理能力。

下面我们练习一道 非常综合 的题目,一题顶三题

题目

给定两个可能 有环 可能 无环 的单链表,头节点分别为 head1 和 head2 。

实现如下功能:如果两个链表相交,返回相交的第一个节点;如果不相交,返回 null 。

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

    // 单链表结构体定义
    public static class Node {
        public int value;
        public Node next;

        public Node(int value) {
            this.value = value;
        }
    }

要想完美解答该题,我们先来做另外几道题。

LeetCode:142.环形链表

给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。不允许修改链表。

直接给出 力扣官方题解 的算法思路,使用 快慢指针 解题:

两个指针 fast 与 slow, 起始指向链表的头部。slow 每次向后移动一个位置, fast 指针向后移动两个位置。如果链表中存在环,则 fast 指针最终将再次与 slow 指针在环中相遇。
AC 此题,链表无敌!!!_第1张图片

设链表中环外部分的长度为 a。slow 指针进入环后,又走了 b 的距离与 fast 相遇。此时,fast 指针已经走完了环的 n 圈,因此它走过的总距离为:
a + n ( b + c ) + b = a + ( n + 1 ) b + n c a+n(b+c)+b=a+(n+1)b+nc a+n(b+c)+b=a+(n+1)b+nc
根据题意,任意时刻,fast 指针走过的距离都为 slow 指针的 2 倍。因此,我们有
a + ( n + 1 ) b + n c = 2 ( a + b ) ⟹ a = c + ( n − 1 ) ( b + c )   a+(n+1)b+nc=2(a+b) ⟹ a=c+(n−1)(b+c)  a+(n+1)b+nc=2(a+b)a=c+(n1)(b+c)
的等量关系,我们会发现:从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。

因此,当 slow 与 fast 相遇时,fast 重新指向链表头部;fast 和 slow 每次向后移动一个位置。最终,它们会在入环点相遇。

    private static Node getLoopNode(Node head) {
        if (head == null || head.next == null || head.next.next == null) {
            return null;
        }
        Node slow = head.next;
        Node fast = head.next.next;
        while (slow != fast) {
            if (fast.next == null || fast.next.next == null) {
                return null;
            }
            slow = slow.next;
            fast = fast.next.next;
        }
        fast = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }

LeetCode:160.相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

AC 此题,链表无敌!!!_第2张图片

思路

如果两个链表相交且长度相同,那么两个链表都从头结点开始遍历,相遇时就找到了相交点;

如果两个链表长度不相同时,若能够也从到达结尾长度相同的点出发时,也一定能够给找到相交点。
AC 此题,链表无敌!!!_第3张图片

因此,只需要将长链表走过一个长度差后,两链表再同时出发即可。
AC 此题,链表无敌!!!_第4张图片

    private static Node noLoop(Node h1, Node h2) {
        if (h1 == null || h2 == null) {
            return null;
        }
        Node cur1 = h1;
        Node cur2 = h2;
        int n = 0;
        while (cur1.next != null) {
            cur1 = cur1.next;
            n++;
        }
        while (cur2.next != null) {
            cur2 = cur2.next;
            n--;
        }
        if (cur1 != cur2) {
            return null;
        }
        cur1 = n > 0 ? h1 : h2;
        cur2 = cur1 == h1 ? h2 : h1;
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }

代码解释

n 来计算两个链表的长度差,cur1 = n > 0 ? h1 : h2;为了后续代码简单起见,将长链表重新用 cur1 指向,较短的链表用 cur2 指向。 cur1 先走过长度差 n 步后,两个链表指针同时出发,当 cur1 == cur2 时,指针指向同一片内存空间,即链表相交了。


上面两道题分别给出了 有环链表找入环点 问题和 无环链表找相交点 问题的解答。相信大家都学会了,接下来,我们来解决 文章开头提出的问题

题目

给定两个可能 有环 可能 无环 的单链表,头节点分别为 head1 和 head2 。实现如下功能:如果两个链表相交,返回相交的第一个节点;如果不相交,返回 null 。

假设两个链表分别是 A 和 B,则可以将该问题分为以下几种情况:

  1. A B 均无环。如果 A B两链表相交,则转化为了 无环链表找相交点 的问题。

  2. A 无环,B 有环;A 有环,B 无环;不可能相交

  3. A B 均有环:

  • 1)A 环 B 环不相交;
    AC 此题,链表无敌!!!_第5张图片

  • 2)A 环 B 环相交,有同一个入口点;
    AC 此题,链表无敌!!!_第6张图片

  • 3)A 环 B 环相交于不同的入口点。
    AC 此题,链表无敌!!!_第7张图片

有了上面两道题的解答,再来解决本问题就轻松很多,首先来看主函数:

    public static Node getIntersectNode(Node h1, Node h2) {
        if (h1 == null || h2 == null) {
            return null;
        }
        Node loop1 = getLoopNode(h1);
        Node loop2 = getLoopNode(h2);
        if (loop1 == null && loop2 == null) {
            return noLoop(h1, h2);
        }
        if (loop1 != null && loop2 != null) {
            return bothLoop(h1, loop1, h2, loop2);
        }
        return null;
    }

代码解释

如果两个链表一个为空,一定不会有环,直接返回空。
调用 getLoopNode 函数分别得到两个链表的入环结点。

loop1loop2 均为空,说明两个链表均为 无环单链表 。转化为了 情况 1,即 LeetCode:160.相交链表 问题。

loop1loop2 均不为空,说明两个链表均为 有环单链表 。转化为了 情况 3,此时就要判断是 情况 3 中的哪一种:

    private static Node bothLoop(Node h1, Node loop1, Node h2, Node loop2) {
        Node cur1 = null;
        Node cur2 = null;
        if (loop1 == loop2) {
            cur1 = h1;
            cur2 = h2;
            int n = 0;
            while (cur1 != loop1) {
                cur1 = cur1.next;
                n++;
            }
            while (cur2 != loop2) {
                cur2 = cur2.next;
                n--;
            }
            cur1 = n > 0 ? h1 : h2;
            cur2 = cur1 == h1 ? h2 : h1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        } else {
            cur1 = loop1.next;
            while (cur1 != loop1) {
                if (cur1 == loop2) {
                    return loop1;
                }
                cur1 = cur1.next;
            }
            return null;
        }
    }

代码解释

我们将 2)和 1)3)分成两大类进行判断:

if 条件进行的是 2)A 环 B 环相交,有同一个入口点 情况的判断。
AC 此题,链表无敌!!!_第8张图片
入口点 作为两个链表的结束,红色范围 左侧 就转化为了 两个单链表找交点 的题目。

因此代码就很好理解了,先计算两个链表长度的差值,长链表先走过差值,之后两指针一起走,相遇即找到 入环点


else 条件进行的是 1)不相交 3)相交于不同的入口点 情况的判断。

1)如果两个环相交,那么 loop1 一定会在 一圈未走完 的情况下与 loop2 相遇。
AC 此题,链表无敌!!!_第9张图片

2)若走了一圈又回到了 loop1 ,则说明两个环一定未相交。

AC 此题,链表无敌!!!_第10张图片

至此,本题就得到了完美的解答。

总结

本题属于多道题目的杂糅,情况较多,需要理清思路一一破解。

通过对本题的分析,既熟练的掌握了 两个单链表的相交 问题,也掌握了 链表有无环 的判断方法。 一举多得!

一定要记得自己写一遍哦!注意边界条件的判断~

你可能感兴趣的:(链表,数据结构,算法,java)