这个是《编程之美》里面的一个题目,给出两个单项链表的头指针,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)); } } }
参考书籍:《编程之美》
相关推荐
链表是数据结构中常见的一种线性...总之,判断链表是否相交的问题可以通过多种算法来解决,每种方法都有其优缺点。在实际应用中,应根据具体需求,如时间复杂度、空间复杂度和是否允许修改链表结构等,选择合适的算法。
- 在判断链表是否相交时,可以用栈存储两个链表的节点,比较栈顶元素来确定交点。 在处理链表问题时,理解链表的基本操作(如遍历、插入、删除)至关重要,同时熟悉不同的算法策略可以帮助我们更高效地解决问题。...
### 面试题一:判断链表中是否存在环 这个问题可以通过快慢指针的方法解决。创建两个指针,一个快指针每次移动两步,另一个慢指针每次移动一步。如果链表存在环,则快慢指针最终会在环内相遇;否则,快指针会先到达...
1. 通过快慢指针寻找链表中点 - 这种方法适用于链表长度未知的情况,可以找到链表的中间节点,通常用于解决链表问题,例如判断链表是否有环。 2. 判断回文链表 - 可以通过前序遍历或利用快慢指针找中点后比较的方式...
经典的多边形填充算法有以下几种: 1. **扫描线算法**:扫描线算法首先确定多边形的顶点,然后按照Y轴顺序排序。对于每一条扫描线,找出与之相交的边,根据边的信息确定填充区域。 2. **扫描转换**:扫描转换算法...
文档中详细介绍了如何在C#和Python中实现双指针,包括以下几种情况: 1.1 C# 和 Python 中的链表结构:双指针通常用于链表操作,如遍历、查找或修改节点。在这部分,文档会讲解如何创建和操作链表,以及如何初始化...
本书通过分析算法在编码前的时间复杂度,帮助学生判断特定解决方案是否可行,并通过精心设计的数据结构和算法实现来优化大型数据集的处理时间。 #### 二、算法分析 在**第二章算法分析**中,作者详细介绍了如何...
判断两个单链表是否相交可以通过计算两个链表的长度差,并让较长的链表先走差值步数,然后同步移动两个链表,看它们是否相遇。 ```csharp public static bool DoListsIntersect(Link head1, Link head2) { Link ...
- **线段相交判断函数**:用于判断两条线段是否相交的函数。 - **判断点 Q 是否在多边形内**:判断一个点是否位于一个多边形内部。 - **计算多边形的面积**:计算多边形的面积。 - **解二次方程 AX^2+BX+C=0**:求解...
标题中的"NFP算法"指的是非交叠特征...总的来说,NFP算法是解决PCB设计中多边形碰撞问题的一种有效方法,通过C++实现,可以实现高效的碰撞检测和布局优化。然而,实际应用中可能需要根据具体需求进行相应的调整和优化。
如果链表相交,最终两个指针会相遇;如果不相交,一个指针会到达链表末尾。 8. **非传统算法问题** - 这些问题更多考察逻辑思维而非纯算法。例如,第一题可以先打开所有开关,等待一段时间后关闭第一个,然后立即...
书中每一章都给出了一个算法、一种算法设计技术、一个应用领域或一个相关的主题。算法是用英语和一种“伪代码”来描述的,任何有一点程序设计经验的人都能看得懂。书中给出了230多幅图,说明各个算法的工作过程。...
3. **相交判断**:如果两个指针相遇,则表示两个链表相交;否则,表示不相交。 #### 八、思维拓展题 **知识点:** - **逻辑思维**:通过逻辑推理解决问题。 - **数学技巧**:运用数学方法和技巧解答问题。 **实现...
书中每一章都给出了一个算法、一种算法设计技术、一个应用领域或一个相关的主题。算法是用英语和一种“伪代码”来描述的,任何有一点程序设计经验的人都能看得懂。书中给出了230多幅图,说明各个算法的工作过程。...
9. **计算几何**:直线与圆的相交、点在多边形内的判断等问题,这些在图形处理和游戏开发中常见。 10. **位运算**:高效地进行数值操作,常用于优化代码性能。 配合上机训练指南,读者可以在实践中加深对这些概念...
1. **判断链表相交**:给定两个链表,判断它们是否在某个节点相交。可以使用双指针法,让两个指针分别从两个链表的头节点开始,如果链表相交,则最终会指向同一个节点。 2. **找到链表的交点**:如果两个链表相交,...
- **两个链表的交集点**:找出两个链表相交的第一个节点。 - **链表循环**:判断一个链表是否存在环。 - **合并分类链表**:对两个已排序的链表进行合并。 - **发现链表循环**:找到链表中环的起始节点。 - **...