一、解决这个问题之前我们需要了解下如何判断一个链表有环?
下面提供二种方法进行实现:
- 使用快慢指针,p1为快指针,p2为慢指针,让这两个指针指向链表的头部,让p1每次走一步,p2每次走两步,如果p1和p2相交,则说明这个链表上面有环;否则,p1和p2中任何一个为null,则说明没有环;
- 使用set集合,将节点往set集合中添加,如果出现不能往set中添加元素的时候,则说明链表是由环的;
二、 判断两个链表是否有交点,输出交点
这个问题分成两种情况,其一是如果链表中不存在环,其二是链表中存在环。
(1)链表中不存在环
基本方法: 先遍历2个链表,求出链表的长度差x;让长链表先走x,后面使得两链表开始跑动,如果遍历的过程中如果出现相等的节点,则链表是有交点的。
代码:
/**
* 定义单链表
*/
public class Node {
private int data;
private Node next;
public Node(int data){
this.data = data;
}
public void setData(int data){
this.data = data;
}
public int getData(){
return data;
}
public Node getNext(){
return next;
}
public void setNext(Node next){
this.next = next;
}
}
/**
*判断单链表是否有环,使用快慢指针算法
*/
public static boolean isHaveLoop(Node node){
Node p1 = node;
Node p2 = node;
while(p1!=null && p2!=null && p2.getNext()!=null){
p1 = p1.getNext();
p2 = p2.getNext().getNext();
if(p1 == p2)
break;
}
return !(p1==null || p2==null); //只要p1或者p2中存在null,则不存在环
}
/**
* 不存在环的时候,1.判断是否有交点 2.返回交点
* (1)计算出两个链表的长度(2)相同位置起始节点是否有交点 isHasPoint并且返回节点
*/
public int getLen(Node node){
int len = 0;
while(node!=null){
len++;
node = node.getNext();
}
return len;
}
public Node getFirstJoinPoint(Node node1,Node node2){
int len1 = getLen(node1);
int len2 = getLen(node2);
int temp = 0;
if(len1>len2){
while(true){
if(temp == len1-len2) //先判断下是否相等
break;
node1 = node1.getNext();
temp++;
}
}else{
while(true){
if(temp == len2-len1)
break;
node2 = node2.getNext();
temp++;
}
}
while(null != node1 && null != node2){
if(node1== node2){
return node1;
}
node1 = node1.getNext();
node2 = node2.getNext();
}
return null;
}
升级版本:如果现在把其中一个链表的首尾相连接,然后只需要判断这个时候另一个单链表是否有环即可。其中需要进行研究问题是如何计算链表进入环的节点。
快指针的速度是慢指针的2倍,所以当假设慢指针走过的路程是:
设环的长度为:,快指针走过的路程是:
化简得:
也就是说链表头到链表进入环的节点之间的距离=快慢指针交点和链表进入环的节点之间的距离+个环长。
所以,现在只要重新初始化一个新的头指针p3,快慢指针交点为p1(此时p1=p2),让p1和p3同步,每次一位去遍历链表,相交节点即为环的入口节点。
(2)如果上述两个单链表中都是存在环的
肯定有人认为是否存在其中一个有环,另一个没有环这样的情况,这样的情况只是存在于两个链表没有交叉的时候。如果两个链表存在交互且还有环,那么必须是环位于交叉点之后的公共部分,要么是这个环刚好都和这两个链表有交叉点。下面是代码的实现。
这个部分的代码是包含没有环的情况的,思路是先对链表进行判断,如果没有环,使用上述代码(下面做了些改进),反之使用下面的改进的代码。
import org.junit.Test;
public class Main {
/**
* 一个链表入环的初始位置
* 需要进行推导,详情见分析
*/
public static Node nodeOfIntoLoop(Node node){
Node p1 = node;
Node p2 = node;
Node p3 = node; //保存头结点
while(p1!=null && p2!=null){
p1 = p1.getNext();
p2 = p2.getNext().getNext();
if(p1 == p2)
break;
}
while(p1!=p3){
p1 = p1.getNext();
p3 = p3.getNext();
}
return p3;
}
/**
* 不存在环的时候,1.判断是否有交点 2.返回交点
* (1)计算出两个链表的长度(2)相同位置起始节点是否有交点 isHasPoint并且返回节点
*/
public int getLen(Node start,Node end){
int len = 0;
while(start!=end){
len++;
start = start.getNext();
}
return len;
}
public Node getFirstJoinPoint(Node node1,Node end1,Node node2,Node end2){
int len1 = getLen(node1,end1);
int len2 = getLen(node2,end2);
int temp = 0;
if(len1>len2){
while(true){
if(temp == len1-len2) //先判断下是否相等
break;
node1 = node1.getNext();
temp++;
}
}else{
while(true){
if(temp == len2-len1)
break;
node2 = node2.getNext();
temp++;
}
}
if(node1== node2){
return node1;
}
while(end1 != node1 && end2 != node2){
node1 = node1.getNext();
node2 = node2.getNext();
if(node1== node2){
return node1;
}
}
return null;
}
/**
* 实现如果存在环的情况下,如何判断出两个单链表是否有交点,并且求出交点
*/
public Node getResult(Node node1,Node node2){
Node IntoLoop1 = nodeOfIntoLoop(node1);
Node IntoLoop2 = nodeOfIntoLoop(node2);
if(null == IntoLoop1 && null == IntoLoop2){ //都没有环
return getFirstJoinPoint(node1,null,node2,null);
}else if((null == IntoLoop1 && null!=IntoLoop2) ||
(null == IntoLoop2 && null!=IntoLoop1)) {
return null;
}else { //两个环中都有交点
if(IntoLoop1 == IntoLoop2){//交点一样,第一种情况
return getFirstJoinPoint(node1, IntoLoop1, node2, IntoLoop2);
}else {
return null; //这种情况下不算是有交点
}
}
}
@Test
public void test(){
Node n1 = new Node(1);
Node n2 = new Node(2);
Node n3 = new Node(3);
Node n4 = new Node(4);
Node n5 = new Node(5);
Node n6 = new Node(6);
Node n7 = new Node(7);
n1.setNext(n4);
n2.setNext(n3);
n3.setNext(n4);
n4.setNext(n5);
n5.setNext(n6);
n6.setNext(n7);
n7.setNext(n6);
Main main = new Main();
System.out.println(main.getResult(n1, n2).getData());
}
}
上述的测试结果是4.
测试用例使用的链表:
参考文献: