编程进阶网编程进阶网
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 基础组成体系
  • 程序编程原理
  • 异常和IO系统
  • 六大设计原则
  • 设计模式导读
  • 创建型设计模式
  • 结构型设计模式
  • 行为型设计模式
  • 设计模式案例
  • 面向对象思想
  • 基础入门
  • 高级进阶
  • JVM虚拟机
  • 数据集合
  • Java面试题
  • C语言入门
  • C综合案例
  • C标准库
  • C语言专栏
  • C++入门
  • C++综合案例
  • C++专栏
  • HTML
  • CSS
  • JavaScript
  • 前端专栏
  • Swift
  • iOS入门
  • 基础入门
  • 开源库解读
  • 性能优化
  • Framework
  • 方案设计
  • 媒体音视频
  • 硬件开发
  • Groovy
  • 常用工具
  • 大厂面试题
  • 综合案例
  • 网络底层
  • Https
  • 网络请求
  • 故障排查
  • 专栏
  • 数组
  • 链表
  • 栈
  • 队列
  • 树
  • 递归
  • 哈希
  • 排序
  • 查找
  • 字符串
  • 其他
  • Bash脚本
  • Linux入门
  • 嵌入式开发
  • 代码规范
  • Markdown
  • 开发理论
  • 开发工具
  • Git管理
  • 百宝箱
  • 开源协议
  • 技术招聘
  • 测试经验
  • 职场提升
  • 技术模版
  • 关于我
  • 目标清单
  • 学习框架
  • 育儿经验
  • 我的专栏
  • 底层能力
  • 读书心得
  • 随笔笔记
  • 职场思考
  • 中华历史
  • 经济学故事
  • 01.单链表创建和使用
  • 02.双链表创建和使用
  • 03.循环链表创建和使用
  • 04.用单向链表实现栈
  • 05.合并两个排序的链表
  • 06.将单链表反转
  • 07.判断单链表是否有环
  • 08.取出有环链表中环的长度
  • 09.链表中环的入口结点
  • 10.两个链表第一个公共结点
  • 11.删除链表的倒数第N个节点
  • 12.获得单链表倒数第k个结点
  • 13.单链表中取出环的起始点
  • 14.两个单链表相交第一个交点
  • 15.复杂链表的复制
  • 16.求单链表中节点的个数
  • 17.从尾到头打印链表

14.两个单链表相交第一个交点

目录介绍

  • 01.题目要求
  • 02.问题分析
  • 03.实例代码

01.题目要求

  • 判断两个单链表相交的第一个交点

02.问题分析

  • 很多人碰到这道题的第一反应是:在第一个链表上顺序遍历每个结点,每遍历到一个结点的时候,在第二个链表上顺序遍历每个结点。如果在第二个链表上有一个结点和第一个链表上的结点一样,说明两个链表在这个结点上重合。显然该方法的时间复杂度为O(len1 * len2)。
  • 方法1:采用栈的思路
    • 可以看出两个有公共结点而部分重合的链表,拓扑形状看起来像一个Y,而不可能是X型。 如下图所示:
    • image
      image
    • 如上图所示,如果单链表有公共结点,那么最后一个结点(结点7)一定是一样的,而且是从中间的某一个结点(结点6)开始,后续的结点都是一样的。
    • 现在的问题是,在单链表中,我们只能从头结点开始顺序遍历,最后才能到达尾结点。最后到达的尾节点却要先被比较,这听起来是不是像“先进后出”?于是我们就能想到利用栈的特点来解决这个问题:分别把两个链表的结点放入两个栈中,这样两个链表的尾结点就位于两个栈的栈顶,接下来比较下一个栈顶,直到找到最后一个相同的结点。
    • 这种思路中,我们需要利用两个辅助栈,空间复杂度是O(len1+len2),时间复杂度是O(len1+len2)。和一开始的蛮力法相比,时间效率得到了提高,相当于是利用空间消耗换取时间效率。
  • 方法2:判断两个链表相交的第一个结点:用到快慢指针,推荐(更优解)
    • 之所以用到栈,是因为我们想同时遍历到达两个链表的尾结点。其实为解决这个问题我们还有一个更简单的办法:首先遍历两个链表得到它们的长度。在第二次遍历的时候,在较长的链表上走 |len1-len2| 步,接着再同时在两个链表上遍历,找到的第一个相同的结点就是它们的第一个交点。
    • 这种思路的时间复杂度也是O(len1+len2),但是我们不再需要辅助栈,因此提高了空间效率。

03.实例代码

  • 代码如下所示
    public class LinkList {
        public Node head;
        public Node current;
    
        public int size;
    
        //方法:向链表中添加数据
        public void add(int data) {
            //判断链表为空的时候
            if (head == null) {//如果头结点为空,说明这个链表还没有创建,那就把新的结点赋给头结点
                head = new Node(data);
                current = head;
            } else {
                //创建新的结点,放在当前节点的后面(把新的结点合链表进行关联)
                current.next = new Node(data);
                //把链表的当前索引向后移动一位
                current = current.next;   //此步操作完成之后,current结点指向新添加的那个结点
            }
        }
    
    
        //方法重载:向链表中添加结点
        public void add(Node node) {
            if (node == null) {
                return;
            }
            if (head == null) {
                head = node;
                current = head;
            } else {
                current.next = node;
                current = current.next;
            }
        }
    
    
        //方法:遍历链表(打印输出链表。方法的参数表示从节点node开始进行遍历
        public void print(Node node) {
            if (node == null) {
                return;
            }
    
            current = node;
            while (current != null) {
                System.out.println(current.data);
                current = current.next;
            }
        }
    
        
        //方法:求两个单链表相交的第一个交点
        public Node getFirstCommonNode(Node head1, Node head2) {
            if (head1 == null || head == null) {
                return null;
            }
            int length1 = getLength(head1);
            int length2 = getLength(head2);
            int lengthDif = 0;  //两个链表长度的差值
            Node longHead;
            Node shortHead;
            //找出较长的那个链表
            if (length1 > length2) {
                longHead = head1;
                shortHead = head2;
                lengthDif = length1 - length2;
            } else {
                longHead = head2;
                shortHead = head1;
                lengthDif = length2 - length1;
            }
            //将较长的那个链表的指针向前走length个距离
            for (int i = 0; i < lengthDif; i++) {
                longHead = longHead.next;
            }
            //将两个链表的指针同时向前移动
            while (longHead != null && shortHead != null) {
                if (longHead == shortHead) { //第一个相同的结点就是相交的第一个结点
                    return longHead;
                }
                longHead = longHead.next;
                shortHead = shortHead.next;
            }
            return null;
        }
    
    
        //方法:获取单链表的长度
        public int getLength(Node head) {
            if (head == null) {
                return 0;
            }
            int length = 0;
            Node current = head;
            while (current != null) {
                length++;
                current = current.next;
            }
            return length;
        }
    
        class Node {
            //注:此处的两个成员变量权限不能为private,因为private的权限是仅对本类访问。
            int data; //数据域
            Node next;//指针域
            public Node(int data) {
                this.data = data;
            }
        }
    }
贡献者: yangchong211
上一篇
13.单链表中取出环的起始点
下一篇
15.复杂链表的复制