面试算法:获取重合列表的第一个相交节点

2024-04-30 22:38

本文主要是介绍面试算法:获取重合列表的第一个相交节点,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

更详细的讲解和代码调试演示过程,请参看视频
如何进入google,算法面试技能全面提升指南

给定两个单向链表,这两个链表有可能会有重叠,情况如下:

这里写图片描述

两个单向链表从节点5开始重合,要求给定一个空间复杂度为O(1)的算法,返回两个链表相交时的第一个节点。依据上图,也就是返回节点5.

首先我们需要做的是,确保给定的两个单向链表,他们是相交的。这个很好确定,只要从头遍历两个链表,如果他们的尾节点是一样的话,那么这两个链表就是相交的,问题是,如何尽快找到他们相交的第一个节点。

最笨的办法是,先找到尾节点,然后去掉尾节点,然后再次遍历查找新的尾节点,然后再去掉,直到两个链表没有共同的尾节点,那么最后去掉的共同尾巴节点,则是两个链表的首次相交节点。这种做法可行,但是算法复杂度是O(n^2)。有没有更好的办法呢?

假设第一个链表,从头结点到首次相交节点,所经历的距离用T1来表示,根据上图例子,T1 = 5, 也就是第一个队列从头结点0开始,需要经历节点1,2,3,4也就是总共5个节点后才能到达节点5.

我们用T3 表示队列2从头节点开始,到达首次相交节点的距离。根据上图,T3 = 3.

我们用T2表示两队列相交部分的节点数.依据上图T2 = 5.

由此队列1的长度为: T1 + T2 (1)
队列2的长度为:T3 + T2 (2)

如果我们能算出T3的数值,那么我们从队列2的头结点出发,经过T3-1步后,就能达到首次相交节点。我们如何计算T3的数值呢?

对T3的计算,需要一个小技巧.我们把队列2进行反转,得到下面情形:

这里写图片描述

如果此时我们从队列1的头结点开始进行遍历,那么从上头的节点0开始出发,会到队列2的头结点0结束。这样,在反转后,如果再次从头遍历队列1的话,得到的长度就是:

T1 + T3 + 1 (3).

根据上面三个公式,我们便可以计算出T3来。

(3) - (1) = T1 + T3 + 1 - T1 - T2 = T3 - T2 + 1
(3) - (1) + (2) = T3 - T2 + 1 + T3 + T2 = 2*T3 + 1

由此,我们可以反解出T3, 有了T3,我们便可以得到两队列首次相交节点了。

这个算法除了需要遍历两个队列外,还需要对其中一个队列进行反转,无论是遍历还是反转,其算法复杂度都是O(n), 因此总算法复杂度是O(n).

代码实现:


public class ListIntersetChecker {private Node listHead1;private Node listHead2;private int firstListLen = 0;  //t1 + t2private int secondListLen = 0; // t3 + t2private int lenAfterReverse = 0; // t1 + t3private ListReverse listReverse = null;public ListIntersetChecker(Node listHead1, Node listHead2) {this.listHead1 = listHead1;this.listHead2 = listHead2;}public Node getFirstIntersetNode() {if (isTwoListInterset() == false) {return null;}listReverse = new ListReverse(listHead2);Node reverseHead = listReverse.getReverseList();lenAfterReverse = getListLen(listHead1);int t3 = ((lenAfterReverse - firstListLen) + secondListLen - 1) / 2;int steps = secondListLen - t3 - 1;while (steps > 0) {reverseHead = reverseHead.next;steps--;}return reverseHead;}private int getListLen(Node head) {int len = 0;while (head != null) {head = head.next;len++;}return len;}private boolean isTwoListInterset() {Node head1 = listHead1;Node head2 = listHead2;while (head1.next != null || head2.next != null) {if (head1.next != null) {head1 = head1.next;firstListLen++; }if (head2.next != null) {head2 = head2.next;secondListLen++;}}firstListLen++;secondListLen++;return head1 == head2;}}

ListIntersetCheck.java 用于实现上面描述的算法。 getFirstIntersetNode返回两重叠队列首次相交节点。isTwoListInterset 用于判断两队列是否相交。在遍历两队列时,统计两队列的长度,也就是获得 T1 + T2 以及 T3 + T2的值。

然后把队列2进行反转,反转后,再从队列1的头节点进行遍历,得到的lenAfterReverse就是 T1 + T3 + 1.

int t3 = ((lenAfterReverse - firstListLen) + secondListLen - 1) / 2;
上面语句则根据前面的推导计算出T3.

由于队列2已经反转了,所以不能从队列2的头结点去遍历,只能从队列2的尾节点开始遍历,如果头结点开始遍历需要T3步的话,那么从尾节点遍历,则需要steps = secondListLen - (T3 + 1) 步。

由此,代码从队列2反转后的头结点开始,经过steps个节点后抵达两队列首次相交时的节点。

再看看主入口代码:


public class LinkList {public static void main(String[] args) {ListUtility util1 = new ListUtility();ListUtility util2 = new ListUtility();Node list1 = util1.createList(10);Node list2 = util2.createList(3);Node node = util1.getNodeByIdx(5);Node tail = util2.getNodeByIdx(2);tail.next = node;ListIntersetChecker intersetChecker = new ListIntersetChecker(list1, list2);Node interset = intersetChecker.getFirstIntersetNode();System.out.println("The first interset node is : " + interset.val);}
}

程序启动时,先构造两个队列,队列1节点从0到9,队列2从0到2,然后把队列2的尾节点的next指向队列1的编号为5的节点,于是就构造了我们例子图中的两个相交队列,然后再利用ListIntersetChecker获得两重合队列的首个相交节点。

最后程序运行结果为:
The first interset node is : 5
结果跟我们理论推导一致,也就是说,我们的说法实现是正确的。更详细的代码讲解和推导调试过程,请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:
这里写图片描述

这篇关于面试算法:获取重合列表的第一个相交节点的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/950095

相关文章

Python MySQL如何通过Binlog获取变更记录恢复数据

《PythonMySQL如何通过Binlog获取变更记录恢复数据》本文介绍了如何使用Python和pymysqlreplication库通过MySQL的二进制日志(Binlog)获取数据库的变更记录... 目录python mysql通过Binlog获取变更记录恢复数据1.安装pymysqlreplicat

C#实现获取电脑中的端口号和硬件信息

《C#实现获取电脑中的端口号和硬件信息》这篇文章主要为大家详细介绍了C#实现获取电脑中的端口号和硬件信息的相关方法,文中的示例代码讲解详细,有需要的小伙伴可以参考一下... 我们经常在使用一个串口软件的时候,发现软件中的端口号并不是普通的COM1,而是带有硬件信息的。那么如果我们使用C#编写软件时候,如

C#实现WinForm控件焦点的获取与失去

《C#实现WinForm控件焦点的获取与失去》在一个数据输入表单中,当用户从一个文本框切换到另一个文本框时,需要准确地判断焦点的转移,以便进行数据验证、提示信息显示等操作,本文将探讨Winform控件... 目录前言获取焦点改变TabIndex属性值调用Focus方法失去焦点总结最后前言在一个数据输入表单

Python实现将实体类列表数据导出到Excel文件

《Python实现将实体类列表数据导出到Excel文件》在数据处理和报告生成中,将实体类的列表数据导出到Excel文件是一项常见任务,Python提供了多种库来实现这一目标,下面就来跟随小编一起学习一... 目录一、环境准备二、定义实体类三、创建实体类列表四、将实体类列表转换为DataFrame五、导出Da

通过C#获取PDF中指定文本或所有文本的字体信息

《通过C#获取PDF中指定文本或所有文本的字体信息》在设计和出版行业中,字体的选择和使用对最终作品的质量有着重要影响,然而,有时我们可能会遇到包含未知字体的PDF文件,这使得我们无法准确地复制或修改文... 目录引言C# 获取PDF中指定文本的字体信息C# 获取PDF文档中用到的所有字体信息引言在设计和出

Python中的随机森林算法与实战

《Python中的随机森林算法与实战》本文详细介绍了随机森林算法,包括其原理、实现步骤、分类和回归案例,并讨论了其优点和缺点,通过面向对象编程实现了一个简单的随机森林模型,并应用于鸢尾花分类和波士顿房... 目录1、随机森林算法概述2、随机森林的原理3、实现步骤4、分类案例:使用随机森林预测鸢尾花品种4.1

python中os.stat().st_size、os.path.getsize()获取文件大小

《python中os.stat().st_size、os.path.getsize()获取文件大小》本文介绍了使用os.stat()和os.path.getsize()函数获取文件大小,文中通过示例代... 目录一、os.stat().st_size二、os.path.getsize()三、函数封装一、os

Python中列表的高级索引技巧分享

《Python中列表的高级索引技巧分享》列表是Python中最常用的数据结构之一,它允许你存储多个元素,并且可以通过索引来访问这些元素,本文将带你深入了解Python列表的高级索引技巧,希望对... 目录1.基本索引2.切片3.负数索引切片4.步长5.多维列表6.列表解析7.切片赋值8.删除元素9.反转列表

python获取当前文件和目录路径的方法详解

《python获取当前文件和目录路径的方法详解》:本文主要介绍Python中获取当前文件路径和目录的方法,包括使用__file__关键字、os.path.abspath、os.path.realp... 目录1、获取当前文件路径2、获取当前文件所在目录3、os.path.abspath和os.path.re

Java子线程无法获取Attributes的解决方法(最新推荐)

《Java子线程无法获取Attributes的解决方法(最新推荐)》在Java多线程编程中,子线程无法直接获取主线程设置的Attributes是一个常见问题,本文探讨了这一问题的原因,并提供了两种解决... 目录一、问题原因二、解决方案1. 直接传递数据2. 使用ThreadLocal(适用于线程独立数据)