两个相交链表

本文详细探讨了单链表相交问题,包括无环和有环链表的相交判断,提供了判断方法和解决方案,适用于不同类型的链表结构。

两个单链表相交的一系列问题
【题目】 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能
不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。 要求:如果链表1
的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达到O(1)。

单链表的相交的情况:

  • 两个无环单链表相交时,他们的末尾节点一定是相同的
  • 一个有环链表和一个无环链表是无法相交的
  • 两个有环链表有两种相交的方式
    在这里插入图片描述
  1. 判断两个链表是有环还是无环

方式一:判断链表是否有环可以通过一个map来实现:遍历链表,判断该节点是否在map,如果存在,则表明该节点是入环的节点;如果不存在,就把该节点放入map集合中,遍历完都没有碰见相同的节点,则表明该链表无环。

方式二:设置两个指针,一个快指针一次走两步、一个慢指针一次走一步。遍历链表,如果两个指针相遇了,即表明该链表是有环的,而且相遇的节点在环上,并且该节点到入环点的距离与头节点到入环点的距离是相等的,那么可以让快指针重新指向头节点,速度与慢指针一样一次一步,当两个指针再次相遇时,则表明相遇的节点即为入环点;如果快指针走到头了(null),则表明该链表是无环链表。

  1. 根据有环无环的情况分出三类情况分别进行判断
  1. 判断两个无环单链表是否相交
      分别遍历两个无环单链表,得出各自的长度和尾节点。
      如果尾节点相等,则表明相交,此时拿长链表的长度减去短链表的长度,长链表先走这个差值的步数,然后两个链表一起走,他们相遇时的节点即为相交节点
      如果两个链表的尾节点不相等,则表明这两个单链表不相交。
  1. 判断两个有环单链表是否相交
      先比较两个有环单链表的入环节点,如果入环节点相等,则表明两个链表是相交的,为图中的第二种情况,此时除去环,就是两个无环单链表的相交的情况,同上的步骤
      如果入环节点不相等,则可能是相交也可能是不相交的,此时遍历其中一个单链表,如果在遍历的过程中碰到了另外一个单链表的入环节点,则表明这两个有环单链表是相交的(图中第三中情况,无论哪个入环节点都可以算作第一个相交点);遍历完后,都没有碰见另外一个链表的入环节点,则表明是不相交的。
  1. 一个无环单链表和一个有环单链表无法相交
package struct;

public class FindFirstIntersectNode {

    //节点类
    public static class Node {

        int data;
        Node next;

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

    }

    //主体方法
    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 noLoop(head1,head2);
        } else if(loop1 != null && loop2 != null){      //两个都是有环列表
            return bothLoop(head1,loop1,head2,loop2);
        } else {                                        //一个有环列表,一个无环列表:绝不会相交
            return null;
        }
    }

    //获取一个单链表的入环节点;没有环,则返回null
    public static Node getLoopNode(Node head) {
        if(head == null || head.next == null|| head.next.next == null){
            return null;
        }

        //设置两个指针
        Node small = head.next;
        Node fast = head.next.next;

        //如果有环,快指针与慢指针终会在环上相遇
        while(small != fast){
            //快指针走到头了,还没相遇,则表明没有环
            if(fast.next == null || fast.next.next == null){
                return null;
            }
            small = small.next;
            fast = fast.next.next;
        }

        //相遇后,快指针指向头节点,且与慢指针保持一样的速度,那么它们会在如环节点相遇
        fast = head;
        while(small != fast){
            small = small.next;
            fast = fast.next;
        }

        //返回入环节点
        return small;

    }

    //两个链表都是没有环
    public static Node noLoop(Node head1, Node head2) {
        if(head1 == null || head2 == null){
            return null;
        }

        Node cur1 = head1;
        Node cur2 = head2;
        int len = 0;

        //得到链表的长度,并且得到尾节点
        while(cur1.next != null){
            cur1 = cur1.next;
            len++;
        }

        //得到该链表与上一个链表的长度的差值,并且得到尾节点
        while(cur2.next != null){
            cur2 = cur2.next;
            len--;
        }

        //两个单链表的尾节点不相等,则表明没有相交
        if(cur1 != cur2){
            return null;
        }

        //两个单链表相交
        cur1 = len > 0 ? head1 : head2;                 //cur1指向长链表的头结点
        cur2 = cur1 == head1 ? head2 : head1;           //cur2指向另外一个链表的头结点
        len = Math.abs(len);                            //长度差值取绝对值

        //先让长链表走差值步
        while(len != 0){
            cur1 = cur1.next;
            len--;
        }

        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }

        //得到入环节点
        return cur1;

    }

    //两个链表都是有环链表
    public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2){

        Node cur1 = null;
        Node cur2 = null;

        //入环节点相等时,两个单链表相交
        if(loop1 == loop2){
            cur1 = head1;
            cur2 = head2;
            int len = 0;

            while(cur1 != loop1){
                len++;
                cur1 = cur1.next;
            }

            while(cur2 != loop2){
                len--;
                cur2 = cur2.next;
            }

            cur1 = len > 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            len = Math.abs(len);

            while(len != 0){
                cur1 = cur1.next;
                len--;
            }
            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;
        }
    }

    public static void main(String[] args) {
        // 1->2->3->4->5->6->7->null
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);

        // 0->9->8->6->7->null
        Node head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).data);

        // 1->2->3->4->5->6->7->4...
        head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(4);
        head1.next.next.next.next = new Node(5);
        head1.next.next.next.next.next = new Node(6);
        head1.next.next.next.next.next.next = new Node(7);
        head1.next.next.next.next.next.next = head1.next.next.next; // 7->4

        // 0->9->8->2...
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next; // 8->2
        System.out.println(getIntersectNode(head1, head2).data);

        // 0->9->8->6->7->4->5->6..
        head2 = new Node(0);
        head2.next = new Node(9);
        head2.next.next = new Node(8);
        head2.next.next.next = head1.next.next.next.next.next; // 8->6
        System.out.println(getIntersectNode(head1, head2).data);

    }

}

下面依次为三个例子的实例
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

<think>首先,用户的问题是:“检测两个链表是否相交算法”。用户提供了几个引用,我需要基于这些构建回答。用户说“参考站内引用”,所以我应该引用这些来源。 用户提供的引用: - [^1]: 解释链表相交的定义。 - [^2]: 左神算法,涉及链表是否有环、两无环链表是否相交等。提到了计算长度差的方法。 - [^3]: 通过比较尾结点来判断是否相交。 - [^4]: 类似[^2],提到了计算长度差的方法。 用户强调:“上述引用仅供参考,并不是用户真正的需求,请尽可能多的参考用户上一次的问题和你的回答。” 但在这个对话中,这是第一次用户的问题,所以我应该专注于当前问题。系统指令说“回答中引用的段落末尾自然地添加引用标识”,所以我必须在回答中引用这些来源。 系统级指令: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答结构清晰,逐步解决问题。 用户的问题是关于检测两个链表是否相交算法。我需要提供一个清晰的、逐步的解释。 关键点从引用中: - 判断两个链表是否相交:检查是否有公共节点。 - 方法1(从[^3]):比较尾结点。如果尾结点相同,则相交;否则,不相交。 - 方法2(从[^2]和[^4]):计算链表长度差,移动长链表的指针到相同起始点,然后同步遍历比较节点。 此外,[^2]提到了链表是否有环的问题,但用户的问题似乎专注于无环链表,因为引用[^1]说“单链表”,假设无环。我应该确认是否是单链表无环的情况。 用户说“检测两个链表是否相交”,没有指定是否有环。引用[^2]提到了有环和无环的情况。但为了简单,我应该先从无环链表开始。 步骤: 1. 确认链表类型:假设是单链表,可能无环或有环。 2. 对于无环链表: - 方法A(尾结点比较):如果两个链表的尾结点相同,则相交;否则,不相交。 - 方法B(长度差):计算两个链表的长度差,移动长链表的头指针到相同位置,然后同步遍历直到找到相同节点或结束。 3. 如果有环,情况更复杂,需要先判断是否有环。 但用户的问题可能针对简单情况。我应该从基础开始。 回答结构: - 简介:解释什么是链表相交。 - 算法描述:逐步解释检测方法。 - 代码示例:提供伪代码或编程语言代码。 - 相关问题:生成相关问题。 使用中文回答。 生成相关问题在最后。 现在,算法描述: 1. **尾结点比较法**(简单有效): - 遍历链表1到尾部,保存尾结点。 - 遍历链表2到尾部,保存尾结点。 - 比较两个尾结点:如果相同,则链表相交;否则,不相交。 - 时间复杂度:O(n+m),其中n和m是链表长度。 - 空间复杂度:O(1),只需存储尾结点。 2. **长度差法**(可以找到相交点): - 计算链表1的长度len1和尾结点。 - 计算链表2的长度len2和尾结点。 - 如果尾结点不同,则不相交;否则,相交。 - 然后,为了找到相交点:计算长度差diff = |len1 - len2|。 - 让长链表的指针先移动diff步。 - 然后两个指针同步移动,直到找到相同节点。 - 时间复杂度:O(n+m)。 - 空间复杂度:O(1)。 尾结点比较法可以判断是否相交,但不能直接找到相交点。长度差法可以判断并找到相交点。 用户的问题是“检测是否相交”,所以尾结点比较法可能足够。 从效率上,两者都高效。 现在,代码示例:我应该提供伪代码或具体语言代码。用户引用中提到C++和Java,所以我可以选择一个通用语言或伪代码。 生成相关问题:如“如何找到相交点?”,“如果链表有环怎么办?”等。 添加引用:在相关段落末尾添加引用标识。 数学表达式:链表问题通常不需要数学,但如果有公式,比如长度差,可以使用行内格式。 例如:计算长度差diff = |len1 - len2|,写成行内数学:$diff = |len1 - len2|$。 独立公式可能不需要。 回答结构: - 问题背景 - 方法1:尾结点比较(引用[^3]) - 方法2:长度差法(引用[^2]和[^4]) - 其他情况(如有环) - 总结 - 代码示例 - 相关问题 其他情况:链表可能有环,引用[^2]讨论了有环链表相交的问题。我应该简要提及。 但保持焦点在用户需求上:检测是否相交。 </think>### 检测两个链表是否相交算法 在计算机科学中,判断两个链表是否相交是一个常见的问题,特别是在链表操作中(如链表合并、循环检测等)。链表相交指的是两个链表是否存在至少一个公共节点。如果存在公共节点,那么从这个节点开始,后续所有节点都是相同的,因为单链表的每个节点只有一个指向下一个节点的指针[^1]。下面我将逐步解释检测算法,确保结构清晰、可靠。算法主要针对无环单链表(最常见情况),最后会简要讨论有环链表的情况。时间复杂度通常为$O(n + m)$(其中$n$和$m$是链表长度),空间复杂度为$O(1)$。 #### 核心算法:无环链表检测 对于无环单链表,有两种高效算法可以检测是否相交。两种方法都基于一个关键原理:如果链表相交,它们必有相同的尾结点(即链表末尾节点)。因此,判断尾结点是否相等是基础步骤。以下是详细步骤: 1. **尾结点比较法(简单高效,仅判断是否相交)** 这种方法通过比较两个链表的尾结点来直接判断是否相交。如果尾结点相同,则链表相交;否则,不相交算法步骤如下: - 遍历链表1,找到尾结点(即最后一个节点)。 - 遍历链表2,找到尾结点。 - 比较两个尾结点: - 如果尾结点相同(内存地址相等),则链表相交(因为相交链表共享相同的尾部)。 - 如果尾结点不同,则链表相交。 - 时间复杂度:$O(n + m)$(每个链表遍历一次)。 - 空间复杂度:$O(1)$(仅需存储尾结点指针)。 - 优点:实现简单,代码易读。 - 缺点:只能判断是否相交,无法直接找到相交点(首个公共节点)。 - 伪代码示例(基于引用[^3]): ```java // 辅助函数:找到链表的尾结点 Node findEnd(Node head) { if (head == null) return null; Node current = head; while (current.next != null) { current = current.next; } return current; } // 检测是否相交 boolean isIntersect(Node head1, Node head2) { Node end1 = findEnd(head1); // 找到链表1的尾结点 Node end2 = findEnd(head2); // 找到链表2的尾结点 return end1 == end2; // 比较尾结点是否相同 } ``` 2. **长度差法(可同时判断相交和找到相交点)** 这种方法在尾结点比较基础上,额外计算链表长度差,从而不仅能判断是否相交,还能找到相交点。算法步骤如下: - 遍历链表1,计算长度$len1$并保存尾结点。 - 遍历链表2,计算长度$len2$并保存尾结点。 - 比较尾结点: - 如果尾结点不同,则链表相交(直接返回失败)。 - 如果尾结点相同,则链表相交(继续下一步)。 - 计算长度差:$diff = |len1 - len2|$。 - 移动长链表的头指针$diff$步(例如,如果$len1 > len2$,将链表1的指针移动$diff$步)。 - 同步遍历两个链表:每次移动两个链表的指针各一步。 - 当指针指向同一个节点时,该节点就是首个相交点。 - 如果遍历到末尾(null)仍未找到相同节点,则无相交(理论上不会发生,因为尾结点相同已确保相交)。 - 时间复杂度:$O(n + m)$(遍历长度和同步遍历各一次)。 - 空间复杂度:$O(1)$(只需存储长度和指针)。 - 优点:可找到相交点,适用于需要定位的场景。 - 缺点:代码稍复杂。 - 伪代码示例(基于引用[^2]和[^4]): ```java // 辅助函数:计算链表长度和尾结点 int[] getLengthAndEnd(Node head) { if (head == null) return new int[]{0, null}; int len = 1; Node current = head; while (current.next != null) { current = current.next; len++; } return new int[]{len, current}; // 返回长度和尾结点 } // 检测是否相交(返回相交点或null) Node findIntersection(Node head1, Node head2) { // 获取链表1的长度和尾结点 int[] info1 = getLengthAndEnd(head1); int len1 = info1[0]; Node end1 = info1[1]; // 获取链表2的长度和尾结点 int[] info2 = getLengthAndEnd(head2); int len2 = info2[0]; Node end2 = info2[1]; // 比较尾结点:如果不同,则不相交 if (end1 != end2) { return null; } // 计算长度差,移动长链表的指针 Node p1 = head1; Node p2 = head2; int diff = Math.abs(len1 - len2); if (len1 > len2) { for (int i = 0; i < diff; i++) { p1 = p1.next; } } else if (len2 > len1) { for (int i = 0; i < diff; i++) { p2 = p2.next; } } // 同步遍历直到找到相交点 while (p1 != p2) { p1 = p1.next; p2 = p2.next; } return p1; // p1 和 p2 相等时即为相交点 } ``` #### 其他情况:有环链表的处理 如果链表可能含有环(即节点指向自身或先前节点),
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值