两个单链表相交和有无环的一系列问题(算法笔记-链表)

【题目】给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一节点,否则返回null

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

1 分析:

解决此问题,需要有两个步骤,第一步是判断链表是否有环,第二个是判断两个链表是否相交;由此会产生三种情况,

    • 情况一:链表A和B均是无环链表,A和B是否相交
    • 情况二:其中一个链表无环,另外一个有环,A和B不可能相交,此情况可忽略
    • 情况三:两个都是有环链表,A和B是否相交

2 判断链表是否有环的两个方法(解决步骤一):

2.1 方法一:

思想:

利用Hash表,每遍历到一个结点时,先判断该结点是否已经存在于Hash表中,如果存在就说明该链表有环,否则就将该结点放入到HashSet数据结构中去,就这样直到遍历到空结点。

优缺点:

因为会使用额外的数据结构,容易实现,但是空间复杂度较大, 推荐在笔试中使用,能加快答题速度但是面试不推荐

代码:

// 本题代码中自写的单链表数据结构
static class Node {
    int value;
    Node next;
    Node(int val) {
        value = val ;
    }
}

public static ListNode getLoopNode(ListNode head)
{
    HashSet nodeSet = new HashSet<>();
    ListNode cur = head;
    while (cur != null)
    {
        if (!nodeSet.contains(cur))
        {
            nodeSet.add(cur);
        }
        else return cur;
        cur = cur.next;
    }
    return null;
}

2.2 方法二

思想:

(1)利用快慢指针解决,快指针跳两步,慢指针每次跳两步,如果有环,那么快指针一定会在环内和慢指针相遇,

(2)而且慢指针最多会在环内走两圈,一定会和快指针相遇;

(3)如果相遇后,快指针回到链首,慢指针位置不变,两者每次都走一步,则她们一定会在环的入口处相遇

(4)如果快慢指针刚好在环的入口处相遇,那么环内和环外结点的数量刚好相等

证明:设环外m,环内n节点,相遇时慢指针在环内走k步。此时慢走m+k步,快比慢多走m+k(快在绕圈圈等着慢),结论(m+k)%n=0。快回起点走m步到环入口,此时慢在环内走了(k+m步)%n=0,即在环入口相遇

优缺点:

空间复杂度低,实现稍微复杂一些,适合面试的时候向面试官展示你的风采

代码

public static Node getLoopNode2(Node head)
{
    if (head == null || head.next == null || head.next.next == null )
        return null;

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

    fast = head;
    while (fast != slow)
    {
        fast = fast.next;
        slow = slow.next;
    }
    return fast;
}

3 求两链表交点的算法实现

3.1 情况一实现:

当两个链表都是无环链表时:

public static Node findIntersectionWithoutLoop(Node head1, Node head2)
{
    if (head1 == null || head2 == null) return null;
    int n = 0;
    Node cur1 = head1;
    Node cur2 = head2;
    while (cur1 != null)
    {
        n++;
        cur1 = cur1.next;
    }
    // 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
    while (cur2 != null)
    {
        n--;
        cur2 = cur2.next;
    }

    // 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
    if (cur1 != cur2) return null;

    // 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
    cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
    cur2 = n > 0 ? head2 : head1; // 谁短,谁就变成cur2
    n = Math.abs(n);
    while (n > 0)
    {
        --n;
        cur1 = cur1.next;
    }
    while (cur1 != cur2)
    {
        cur1 = cur1.next;
        cur2 = cur2.next;
    }
    return cur1;
}

3.2 情况二不存在,略

3.3 情况三的实现

如果两个链表都是有环链表:

分析:如果两个链表都有环,要么不相交,要么相交共用环,对于共用环,也有两种情况:

    • 两者共用同一个入环结点,此时他们的相交结点只有一个,如下图甲
    • 两者的入环结点不是同一个, 但此时两个链表分别的入环结点都可以叫做相交结点,如图乙

两个单链表相交和有无环的一系列问题(算法笔记-链表)_第1张图片

代码

// 这里的loop1和loop2分别是指链表head1和head2的入环结点
public static Node findIntersectionWithBothLoop(Node head1, Node loop1, Node head2, Node loop2)
{
    Node cur1 = null;
    Node cur2 = null;
    if (loop1 == loop2)
    {
        int n = 0;
        while (cur1 != loop1)
        {
            n++;
            cur1 = cur1.next;
        }
        // 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
        while (cur2 != loop2)
        {
            n--;
            cur2 = cur2.next;
        }

        // 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
        if (cur1 != cur2) return null;

        // 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
        cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
        cur2 = n > 0 ? head2 : head1; // 谁短,谁就变成cur2
        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; // 返回loop1和loop2都可以,都称为两个链表的第一个相交的结点
            cur1 = cur1.next;
        }
        return null; // 如果cur1的指针转回到环的入口时,则此时两个链表不相交
    }
}

3.4 三种情况合并到一起

// 综合三种情况
public static Node getIntersectNode(Node head1, Node head2)
{
    if (head1 == null || head2 == null) return null;
    // 使用getLoopNode判断属于哪一种情况
    Node loop1 = getLoopNode(head1);
    Node loop2 = getLoopNode(head2);
    // 两个链表都是无环链表
    if (loop1 == null && loop2 == null)
        return findIntersectionWithoutLoop(head1, head2);
    // 如果两个链表都是有环链表
    if (loop1 != null && loop2 != null)
        return findIntersectionWithBothLoop(head1, loop1, head2, loop2);
    return null;
}

3.5 完整代码

package ListNode;
import java.util.HashSet;
public class IsIntersection {
    public static void main(String[] args) {
    }
    static class Node {
        int value;
        Node next;
        Node(int val) {
            value = val ;
        }
    }
    public static Node getLoopNode(Node head)
    {
        if (head == null || head.next == null || head.next.next == null )
            return null;
        HashSet nodeSet = new HashSet<>();
        Node cur = head;
        while (cur != null)
        {
            if (!nodeSet.contains(cur))
            {
                nodeSet.add(cur);
            }
            else return cur;
            cur = cur.next;
        }
        return null;
    }
    public static Node getLoopNode2(Node head)
    {
        if (head == null || head.next == null || head.next.next == null )
            return null;

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

        fast = head;
        while (fast != slow)
        {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }
    public static Node getIntersectNode(Node head1, Node head2)
    {
        if (head1 == null || head2 == null) return null;
        Node loop1 = getLoopNode(head1);
        Node loop2 = getLoopNode(head2);
        // 两个链表都是无环链表
        if (loop1 == null && loop2 == null)
            return findIntersectionWithoutLoop(head1, head2);
        // 如果两个链表都是有环链表
        if (loop1 != null && loop2 != null)
            return findIntersectionWithBothLoop(head1, loop1, head2, loop2);
        return null;
    }
    public static Node findIntersectionWithBothLoop(Node head1, Node loop1, Node head2, Node loop2)
    {
        Node cur1 = null;
        Node cur2 = null;
        if (loop1 == loop2)
        {
            int n = 0;
            while (cur1 != loop1)
            {
                n++;
                cur1 = cur1.next;
            }
            // 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
            while (cur2 != loop2)
            {
                n--;
                cur2 = cur2.next;
            }

            // 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
            if (cur1 != cur2) return null;

            // 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
            cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
            cur2 = n > 0 ? head2 : head1; // 谁短,谁就变成cur2
            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; // 返回loop1和loop2都可以,都称为两个链表的第一个相交的结点
                cur1 = cur1.next;
            }
            return null; // 如果cur1的指针转回到环的入口时,则此时两个链表不相交
        }

    }

    public static Node findIntersectionWithoutLoop(Node head1, Node head2)
    {
        if (head1 == null || head2 == null) return null;
        int n = 0;
        Node cur1 = head1;
        Node cur2 = head2;
        while (cur1 != null)
        {
            n++;
            cur1 = cur1.next;
        }
        // 下面这个while循环后,n的绝对值变为较长链表的长度减去较短链表的值
        while (cur2 != null)
        {
            n--;
            cur2 = cur2.next;
        }

        // 遍历完两个链表后,如果他们的链尾结点不是同一个结点,则直接返回空
        if (cur1 != cur2) return null;

        // 以下两行代码总能使得较长的链表赋值给cur1,较短的赋值给cur2
        cur1 = n > 0 ? head1 : head2; // 谁长,谁就变成cur1
        cur2 = n > 0 ? head2 : head1; // 谁短,谁就变成cur2
        n = Math.abs(n);
        while (n > 0)
        {
            --n;
            cur1 = cur1.next;
        }
        while (cur1 != cur2)
        {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }
}

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