这个是《编程之美》里面的一个题目,给出两个单项链表的头指针,h1、h2判断这2个链表是否相交?
【解法一】直观的想法
循环遍历h1的每一个节点,判断是否存在一个节点在h2中,由于链表无法随机访问,每次查找需要对链表h2遍历最多length(h1)次,因此算法的时间复杂度是O(length(h1)*length(h2)),显然这个方法很耗时间。
【解法二】利用hash计数
①循环遍历h1,计算每个节点的hash值并存入map中;②循环遍历h2,顺序计算每个节点的hash值v,并用map.get(v),若返回非空,则算法结束。
第①步算法时间复杂度O(length(h1)),第②步算法时间复杂度O(length(h2)),因此hash计数 算法时间复杂度为O(max(length(h1),length(h2))),复杂度降低到线性。但是由于使用了额外的map结构,空间复杂度为O(length(h1))。
【解法三】链表连接
我们分析两个单链表相交时,节点的逻辑结构如下:
h1->P1->P2->...->Pi->...->K1->...->Km
h2->Q1->Q2->...->Qi->...->K1->...->Km
可以看到如果把h1链表的尾节点的next指针指向h2链表的第一个节点,那么可以看到如果h1和h2相交,则
h2变成了一个循环单链表,因此只需判断h2是否为循环链表即可。需要遍历h1和h2,因此算法时间负责度O(max(length(h1),length(h2))),空间负责度为O(1)。但是这个算法的缺点是需要链接h1和h2,改变链表的逻辑结构,在多线程环境下需要上锁,影响一定的性能。
【解法四】更简单的做法,直接判断链表末节点是否相同
同解法三的节点分析,如果h1和h2相交于节点K1,那么根据单链表的定义(任何节点有且仅有唯一的后继和唯一的前驱,null也算作前驱和后继吧),因此如果h1和h2相交,那么两个链表从K1以后的后继的节点是相同的。因此判断链表是否相交,只用判断尾节点是否相同即可,只需遍历2个链表,时间复杂度为O(max(length(h1),length(h2))),空间复杂度为O(1)。这个算法很简单优雅,赞一个。
发散思维:
1、如何求解两个相交链表的第一个相交节点?
这个其实也很简单,使用小学数学知识即可解决。
记 L1 = length(h1); L2 = length(h2); h1和h2 由于相交,共有K个节点相同,那么两个链表的长度之差为相交节点之前的节点之差。因此我们让长链表先遍历abs(L1-L2)步,然后让两个链表同时遍历,同时判断2个链表的节点是否相同,若存在相同节点,则返回该节点,算法结束。
2、如果两个链表中本身存在环,该如何解决?
存在环的单链表,类似于这种形式。
如果链表存在环,则上面的算法都无法返回正确结果,陷入死循环,因此首先要把环解开。下一篇日志将介绍几种检测链表是否存在环的算法。
package com.xx.dataStructure; import java.util.HashMap; import java.util.Map; //节点定义 class Node { int data; Node next; Node (int data,Node next){ this.data = data; this.next = next; } @Override public int hashCode(){ return data ; } @Override public boolean equals(Object o){ if (o == null) return false; if (this == o) return true; if (o instanceof Node){ Node node = (Node)o; return this.data == node.data; } return false; } } //使用策略模式 interface Intersect { boolean isIntersect(Node h1,Node h2); } abstract class AbstractIntersect implements Intersect{ protected String name ; public String getName(){ return name; } } //循环遍历法 class FullTraverse extends AbstractIntersect{ { name = " FullTraverse algorithm "; } @Override public boolean isIntersect(Node h1, Node h2) { if (h1 == null || h2 == null) return false; Node i = h1.next; Node j = h2.next; for(;i != null ; i = i.next ){ for(j = h2.next; j != null ; j = j.next){ if (j == i) return true; } } return false; } } //hash计数法 class HashCalculate extends AbstractIntersect { { name = " HashCalculate algorithm "; } @Override public boolean isIntersect(Node h1, Node h2) { if (h1 == null || h2 == null) return false; //init map Map<Integer,Node> map = new HashMap<Integer,Node>(0); Node i = h1.next; while(i != null){ map.put(i.hashCode(), i); i = i.next; } //find the same node Node j = h2.next; while(j != null){ if (map.get(j.hashCode()) != null) return true; j= j.next; } return false; } } //首尾相连法 class LinkHeadAndTail extends AbstractIntersect { { name = " LinkHeadAndTail algorithm "; } @Override public boolean isIntersect(Node h1, Node h2) { if (h1 == null || h2 == null) return false; boolean result = false; Node i = h1.next; Node j = h2.next; while(i != null && i.next != null){ i = i.next; } i.next = j; boolean begin = true; //判断h2是否为循环链表 while(j != null ){ if (j == h2.next && !begin ){ result = true; break; } else { j = j.next; begin = false; } } //链表解除锁定 i.next = null; return result; } } //尾节点判定法 class TailEqual extends AbstractIntersect { { name = " TailEqual algorithm "; } @Override public boolean isIntersect(Node h1, Node h2) { if (h1 == null || h2 == null) return false; Node tail1 = h1.next; Node tail2 = h2.next; while(tail1 != null && tail1.next != null){ tail1 = tail1.next; } while(tail2 != null && tail2.next != null){ tail2 = tail2.next; } if (tail1 == tail2 ) return true; return false; } } public class LinkedList { //相交节点 static Node intersectNode = new Node(8,new Node(9,new Node(10,null))); //h1链表 0->1->2->8->9->10 static Node h1 = new Node(-1,new Node(0,new Node(1,new Node(2,intersectNode)))); //h2链表 0->11->12->13->14->8->9->10 static Node h2 = new Node(-1,new Node(10,new Node(11,new Node(12,new Node(13,new Node(14,intersectNode)))))); static Node h3 = new Node(-1,new Node(0,new Node(1,null))); static Node h4 = new Node(-1,new Node(10,null)); static Node h5 = null; static Node h6 = null; static Node h7 = new Node(-1,new Node(0,null)); static Node h8 = new Node(-1,null); public static void main(String [] args){ AbstractIntersect[] methods = { new FullTraverse(), new HashCalculate(), new LinkHeadAndTail(), new TailEqual() }; for(AbstractIntersect method : methods){ System.out.println(method.getName() + ":"+ method.isIntersect(h1, h2)); System.out.println(method.getName() + ":"+ method.isIntersect(h3, h4)); System.out.println(method.getName() + ":"+ method.isIntersect(h5, h6)); System.out.println(method.getName() + ":"+ method.isIntersect(h7, h8)); } } }
参考书籍:《编程之美》
相关推荐
可以看到如果把h1链表的尾节点的next指针指向h2链表的第一个节点,那么可以看到如果h1和h2相交,则h2变成了一个循环单链表,因此只需判断h2是否为循环链表
书中每一章都给出了一个算法、一种算法设计技术、一个应用领域或一个相关的主题。算法是用英语和一种“伪代码”来描述的,任何有一点程序设计经验的人都能看得懂。书中给出了230多幅图,说明各个算法的工作过程。...
书中每一章都给出了一个算法、一种算法设计技术、一个应用领域或一个相关的主题。算法是用英语和一种“伪代码”来描述的,任何有一点程序设计经验的人都能看得懂。书中给出了230多幅图,说明各个算法的工作过程。...
然后调用IsInArea(point)函数来判断当前点point是否在以当前触点中心点为中心的矩形区域内。如果是,则用一个全局枚举变量put来记录来前触点是两个输入端和一个输出端中哪一个。 我们看这个枚举类型: enum Myput { ...
第五次作业函数第一题--
本项目旨在利用深度学习方法实现作物病害的自动诊断。作物病害是农业生产中的重要问题,及时诊断和处理对于减少产量损失至关重要。 我们采用深度学习算法,通过分析作物的图像,实现对病害的自动识别和分类。项目使用的数据集包括公开的作物病害图像数据集,如ISIC等,并进行了预处理,包括图像增强、分割和特征提取等。 在运行环境方面,我们使用Python编程语言,基于TensorFlow、PyTorch等深度学习框架进行开发。为了提高计算效率,我们还使用了GPU加速计算。此外,我们还采用了Docker容器技术,确保实验结果的可重复性。 项目完成后,将实现对作物病害的快速、准确诊断,为农业生产提供有力支持,有助于减少产量损失。同时,项目成果也可应用于其他图像识别和分类任务。
机械设计CD驱动印刷设备step非常好的设计图纸100%好用.zip
python烟花代码
附件中是一个简单的烟花效果的代码示例: 在Python中,可以使用多种方式来模拟烟花效果,其中一种常用的方法是使用turtle模块,它提供了一个画布和一个小海龟,可以用来绘制各种图形。 这段代码首先导入了turtle模块和random模块,然后在屏幕上绘制了10次烟花爆炸的效果。每次爆炸都是由5个小圆组成,颜色随机选择,圆的大小也是随机的。 请注意,这段代码需要在支持turtle模块的Python环境中运行,并且需要有图形界面的支持。如果你在没有图形界面的环境中(比如某些服务器或者命令行界面),这段代码可能无法正常运行。
商业化产品经理,到底如何实现产品商业化?.docx
Panduit 工业以太网部件内部销售指南
在Java中,实现一个三维装箱(也称为三维背包问题)的算法通常涉及到组合优化和动态规划。这个问题是一个典型的优化问题,其中目标是在三个维度的限制下最大化价值的总和。下面是一个简单的Java代码示例,它使用动态规划来解决三维装箱问题。 请注意,这个代码只是一个简单的示例,它假设所有物品的第三个维度的大小都是1,并且没有给出如何回溯选择物品的完整逻辑。在实际应用中,三维装箱问题可能更加复杂,需要考虑所有三个维度的限制,并且可能需要更复杂的算法来解决。 此外,这个问题的解决方案可能需要根据具体问题的要求进行调整,例如物品是否可以分割、是否允许超过一个的物品等。如果你有特定的问题描述或者需要进一步的帮助,请提供更多的细节。
常用品牌EPLAN部件库
单片机开发的教程可以分为以下几个步骤: 1. 了解单片机基础知识:在学习单片机开发之前,需要了解单片机的相关知识,包括单片机的基本结构、指令系统、编程语言等。 2. 选择开发板:选择一款适合自己学习开发板的型号和厂商,通常需要关注开发板的性价比、开发环境是否友好等因素。 3. 学习开发环境:根据所选的开发板,学习相关的开发环境和使用方法,例如Keil、IAR等集成开发环境。 4. 掌握编程语言:单片机常用的编程语言包括C语言和汇编语言,根据实际情况选择其中一种进行学习。 5. 基础操作:熟悉单片机的引脚定义和IO口配置,了解单片机的启动代码,可以通过修改启动代码进行基本功能调试。 6. 综合实践:根据具体项目需求,进行单片机开发的综合实践。在实践中需要掌握如何编写程序、如何进行硬件调试、如何使用相关工具软件等技能。 下面是一个单片机开发的简单教程介绍: 首先,确定所使用的单片机型号和开发板类型。在这个阶段,需要查阅相关资料,了解开发板的规格书、芯片规格等基本资料。 其次,安装并配置开发环境。根据所选的开发板,安装相应的集成开发环境(IDE),并配置好开发环境。 接着,学习并掌
Q1.ipynb
(自适应手机端)IT网络建站公司pbootcms模板 互联网营销企业网站源码下载.zip
Bematech 激光扫描器用户手册
激励视频接入文档.pdf
java jdk1.8 202版本下载window linux打包
Lite Beam M5快速指南